web.xml
<web-app><!--Listener作用:加载并初始化Spring容器(也可以称为父容器)--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/app-context.xml</param-value></context-param><!--加载并初始化DispatcherServlet(同时也加载并且初始化SpringMVC容器,也称为子容器)--><servlet><servlet-name>app</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value></param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>app</servlet-name><url-pattern>/app/*</url-pattern></servlet-mapping><!--一般情况下只需要一个容器即可,不需要存在这种父子容器,当然这需要根据实际业务需求确定-->
</web-app>
ServletContextListener
在 Servlet API 中有一个 ServletContextListener 接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期。
public interface ServletContextListener extends EventListener {//当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。default void contextInitialized(ServletContextEvent sce) {}
//当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。default void contextDestroyed(ServletContextEvent sce) {}
}
先初始化Spring容器
ContextLoaderListener
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {public ContextLoaderListener() {}public ContextLoaderListener(WebApplicationContext context) {super(context);}//Tomcat启动时,会自动调用该方法,初始化Spring容器public void contextInitialized(ServletContextEvent event) {this.initWebApplicationContext(event.getServletContext());}public void contextDestroyed(ServletContextEvent event) {this.closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}
}
ContextLoader
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {//当前容器已经存在Spring容器,则抛出异常if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");} else {servletContext.log("Initializing Spring root WebApplicationContext");Log logger = LogFactory.getLog(ContextLoader.class);if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {if (this.context == null) {//创建Spring容器this.context = this.createWebApplicationContext(servletContext);}//如果context是 ConfigurableWebApplicationContext 这种类型if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;if (!cwac.isActive()) {//则设置其父容器if (cwac.getParent() == null) {//这里parent 是nullApplicationContext parent = this.loadParentContext(servletContext);cwac.setParent(parent);}//执行Spring容器初始化,加载并初始化Beanthis.configureAndRefreshWebApplicationContext(cwac, servletContext);}}//将Spring放到ServletContext上下文中,使初始化SpringMVC容器时可以拿到Spring容器并且设置为自己的父容器
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;} else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");}return this.context;} catch (Error | RuntimeException var8) {logger.error("Context initialization failed", var8);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);throw var8;}}}
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {Class<?> contextClass = this.determineContextClass(sc);if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");} else {//利用反射创建Spring容器,这时Spring容器中属性都还没有值,仅仅是new了一个对象出来而已return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);}}
初始化SpringMVC容器
DispatcherServlet继承关系
Servlet
// Servlet的加载和实例化可以发生在容器启动时,也可以延迟初始化直到有请求需要处理时
public interface Servlet {// 负责初始化Servlet对象,容器创建好Servlet对象后由容器调用调用,只执行一次// 当load-on-startup设置为负数或者不设置时会在Servlet第一次用到时才被调用void init(ServletConfig var1) throws ServletException;// 获取该Servlet的初始化参数信息ServletConfig getServletConfig();// 负责响应客户端的请求,当容器接收到客户端要求访问特定Servlet对象的请求时,会调用该Servlet对象的service()方法,每次请求都会执行void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;// 返回Servlet信息,包含创建者、版本、版权等信息String getServletInfo();// Servlet结束生命周期时调用,释放Servlet对象占用的资源void destroy();
}
GenericServlet
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {}
HttpServlet
public abstract class HttpServlet extends GenericServlet {}
HttpServletBean
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {public final void init() throws ServletException {// 将init-param映射到bean属性 将<init-param>配置参数自动转换成BeanWrapperPropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));this.initBeanWrapper(bw);bw.setPropertyValues(pvs, true);} catch (BeansException var4) {if (this.logger.isErrorEnabled()) {this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);}throw var4;}}this.initServletBean();}
}
FrameworkServlet
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {protected final void initServletBean() throws ServletException {this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");if (this.logger.isInfoEnabled()) {this.logger.info("Initializing Servlet '" + this.getServletName() + "'");}long startTime = System.currentTimeMillis();try {//初始化SpringMVC容器this.webApplicationContext = this.initWebApplicationContext();this.initFrameworkServlet();} catch (RuntimeException | ServletException var4) {this.logger.error("Context initialization failed", var4);throw var4;}if (this.logger.isDebugEnabled()) {String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);}if (this.logger.isInfoEnabled()) {this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");}}
}
protected WebApplicationContext initWebApplicationContext() {//从Servlet上下文中获取Spring容器WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());WebApplicationContext wac = null;//SpringMVC容器不为nullif (this.webApplicationContext != null) {wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;if (!cwac.isActive()) {//则设置父容器if (cwac.getParent() == null) {cwac.setParent(rootContext);}//加载并初始化SpringMVC中的Beanthis.configureAndRefreshWebApplicationContext(cwac);}}}//根据ContextAttribute找SpringMVC容器if (wac == null) {wac = this.findWebApplicationContext();}//如果没有找到,则创建SpringMVC容器if (wac == null) {wac = this.createWebApplicationContext(rootContext);}//加载并初始化SpringMVC中的Beanif (!this.refreshEventReceived) {synchronized(this.onRefreshMonitor) {//初始化DispatcherServlet及九大组件this.onRefresh(wac);}}if (this.publishContext) {String attrName = this.getServletContextAttributeName();//将SpringMVC容器放入到Servlet上下文中this.getServletContext().setAttribute(attrName, wac);}return wac;}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {Class<?> contextClass = this.getContextClass();if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");} else {//获取利用反射获取SringMVC容器ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);//设置系统环境变量wac.setEnvironment(this.getEnvironment());//设置父容器wac.setParent(parent);//获取web.xml配置的 contextConfigLocationString configLocation = this.getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}//加载初始化Beanthis.configureAndRefreshWebApplicationContext(wac);return wac;}}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {//设置SpringMVC容器id if (ObjectUtils.identityToString(wac).equals(wac.getId())) {if (this.contextId != null) {wac.setId(this.contextId);} else {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());}}
//设置Servlet上下文wac.setServletContext(this.getServletContext());//设置Serlvet一些配置参数wac.setServletConfig(this.getServletConfig());//设置namespacewac.setNamespace(this.getNamespace());//设置ContextRefreshListenerwac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener()));//获取Environment并调用initPropertySources替换servletContextInitParams和servletConfigInitParams参数ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());}
//暂无实现this.postProcessWebApplicationContext(wac);this.applyInitializers(wac);wac.refresh();}
DispatcherServlet
public class DispatcherServlet extends FrameworkServlet {protected void onRefresh(ApplicationContext context) {this.initStrategies(context);}
}
protected void initStrategies(ApplicationContext context) {//初始化上传文件解析器this.initMultipartResolver(context);//初始化国际化相关解析器this.initLocaleResolver(context);//初始化主题解析器this.initThemeResolver(context);//初始化处理器映射器this.initHandlerMappings(context);//初始化处理器适配器this.initHandlerAdapters(context);//初始化处理器异常解析器this.initHandlerExceptionResolvers(context);this.initRequestToViewNameTranslator(context);、//初始化视图解析器this.initViewResolvers(context);this.initFlashMapManager(context);
}
private void initLocaleResolver(ApplicationContext context) {try {//从Spring容器中获取LocaleResolver的实现类this.localeResolver = (LocaleResolver)context.getBean("localeResolver", LocaleResolver.class);if (this.logger.isTraceEnabled()) {this.logger.trace("Detected " + this.localeResolver);} else if (this.logger.isDebugEnabled()) {this.logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());}} catch (NoSuchBeanDefinitionException var3) {this.localeResolver = (LocaleResolver)this.getDefaultStrategy(context, LocaleResolver.class);if (this.logger.isTraceEnabled()) {this.logger.trace("No LocaleResolver 'localeResolver': using default [" + this.localeResolver.getClass().getSimpleName() + "]");}}}
private void initThemeResolver(ApplicationContext context) {try {//从Spring容器中获取ThemeResolver的实现类this.themeResolver = (ThemeResolver)context.getBean("themeResolver", ThemeResolver.class);if (this.logger.isTraceEnabled()) {this.logger.trace("Detected " + this.themeResolver);} else if (this.logger.isDebugEnabled()) {this.logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());}} catch (NoSuchBeanDefinitionException var3) {this.themeResolver = (ThemeResolver)this.getDefaultStrategy(context, ThemeResolver.class);if (this.logger.isTraceEnabled()) {this.logger.trace("No ThemeResolver 'themeResolver': using default [" + this.themeResolver.getClass().getSimpleName() + "]");}}}
private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;
//如果detectAllHandlerMappings为true 则从Spring容器中获取所有实现HandlerMapping接口的实现类if (this.detectAllHandlerMappings) {// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.Map<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());//并且根据@Order注解 进行排序AnnotationAwareOrderComparator.sort(this.handlerMappings);}}else {try {//否则从Spring容器中获取beanName为"handlerMapping"的实现类HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);//并且赋值给handlerMappingsthis.handlerMappings = Collections.singletonList(hm);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerMapping later.}}
//如果handlerMappings还是nullif (this.handlerMappings == null) {//则获取默认配置文件配置的HandlerMapping(DispatcherServlet.properties)this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}
//遍历所有的HandlerMappingsfor (HandlerMapping mapping : this.handlerMappings) {if (mapping.usesPathPatterns()) {//如果mapping 是自动使用路径匹配//则parseRequestPatch设置为truethis.parseRequestPath = true;break;}}}
private void initHandlerAdapters(ApplicationContext context) {this.handlerAdapters = null;
//如果detectAllHandlerAdapters=trueif (this.detectAllHandlerAdapters) {//则从Spring容器中获取所有实现HandlerAdapter接口的实现类Map<String, HandlerAdapter> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerAdapters = new ArrayList<>(matchingBeans.values());// 并且根据@Order注解进行排序AnnotationAwareOrderComparator.sort(this.handlerAdapters);}}else {try {//如果detectAllHandlerAdapters=false,则获取beanName为"handlerAdpater"的handlerApater实现类HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);this.handlerAdapters = Collections.singletonList(ha);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerAdapter later.}}//如果handlerApaters==null 则从获取默认配置文件中配置的实现类if (this.handlerAdapters == null) {this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;
//如果detectAllHandlerExceptionResolvers=true 则从Spring容器中获取所有实现HandlerExeptionResolver接口的实现类if (this.detectAllHandlerExceptionResolvers) {// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());// 根据@Order进行排序AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}else {try {//如果detectAllHandlerExceptionResolvers=false 则从spring容器中获取beanName是"handlerExceptionResolver"的实现类HandlerExceptionResolver her =context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);this.handlerExceptionResolvers = Collections.singletonList(her);}catch (NoSuchBeanDefinitionException ex) {// Ignore, no HandlerExceptionResolver is fine too.}}// 如果handlerExceptionResolvers==null 则获取默认配置文件中默认的实现类if (this.handlerExceptionResolvers == null) {this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
private void initRequestToViewNameTranslator(ApplicationContext context) {try {//从Spring容器中获取beanName是"viewNameTranslator"的实现类this.viewNameTranslator =context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName());}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.viewNameTranslator);}}catch (NoSuchBeanDefinitionException ex) {// 如果抛出异常 则获取默认配置文件中配置的实现类this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);if (logger.isTraceEnabled()) {logger.trace("No RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +"': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]");}}}
private void initViewResolvers(ApplicationContext context) {this.viewResolvers = null;
//如果detectAllViewResolvers=true 则从Spring容器获取ViewResolver的实现类if (this.detectAllViewResolvers) {// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.Map<String, ViewResolver> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.viewResolvers = new ArrayList<>(matchingBeans.values());// 然后根据@Order进行排序AnnotationAwareOrderComparator.sort(this.viewResolvers);}}else {try {//如果detectAllViewResolvers=false 则从Spring容器获取beanName是"viewResolver"的实现类ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);this.viewResolvers = Collections.singletonList(vr);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default ViewResolver later.}}// 如果viewResolvers==null 则获取配置文件中指定默认的ViewResolver实现类if (this.viewResolvers == null) {this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);if (logger.isTraceEnabled()) {logger.trace("No ViewResolvers declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
private void initFlashMapManager(ApplicationContext context) {try {//从Spring容器中获取beanName是"flashMapManager"的实现类this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.flashMapManager);}}catch (NoSuchBeanDefinitionException ex) {// 异常 则从配置文件中获取默认的实现类this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);if (logger.isTraceEnabled()) {logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME +"': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");}}}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {//defaultStrategies为空 则还没有加载过DispatcherServlet.properties文件if (defaultStrategies == null) {try {//根据DispatcherServlet.properties 根据ClassPatchRsourceClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);//将properties文件内容加载到defaultStrategies中defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);}catch (IOException ex) {throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());}}
//根据传入的参数name 作为keyString key = strategyInterface.getName();//从defaultStrategies(配置文件)中查找String value = defaultStrategies.getProperty(key);if (value != null) {//找到 则根据,进行分割,转化为数组String[] classNames = StringUtils.commaDelimitedListToStringArray(value);List<T> strategies = new ArrayList<>(classNames.length);for (String className : classNames) {try {//遍历数组 通过ClassUtils.forName 构建ClassClass<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());//根据class 创建BeanObject strategy = createDefaultStrategy(context, clazz);//添加到strategies集合中strategies.add((T) strategy);}catch (ClassNotFoundException ex) {throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className +"] for interface [" + key + "]", ex);}catch (LinkageError err) {throw new BeanInitializationException("Unresolvable class definition for DispatcherServlet's default strategy class [" +className + "] for interface [" + key + "]", err);}}//并返回该集合return strategies;}else {return Collections.emptyList();}}
DispatcherServlet.properties
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager