1.引
上一节已经简单介绍了ContextLoaderListener的初始化过程,接下来应该初始化DispatcherServlet。DispatcherServlet作为SpringMVC的核心Servlet控制器,了解其初始化机制十分有必要。
Spring MVC框架,与其他很多web的MVC框架一样:请求驱动;所有设计都围绕着一个中央Servlet来展开,它负责把所有请求分发到控制器;同时提供其他web应用开发所需要的功能。不过Spring的中央处理器, DispatcherServlet ,能做的比这更多。它与Spring IoC容器做到了无缝集成,这意味着,Spring提供的任何特性,在Spring MVC中你都可以使用。下图展示了Spring Web MVC的 DispatcherServlet 处理请求的工作流。熟悉设计模式的朋友会发现, DispatcherServlet 应用的其实就是一个“前端控制器”的设计模式( 其他很多优秀的web框架也都使用了这个设计模式) 。
在这里插入图片描述
DispatcherServlet的继承关系如下:
DispatcherServlet-->FrameworkServlet-->HttpServletBean-->HttpServlet,从这里可以看到,DispatcherServlet其本质也还是Servlet。
根据之前对HttpServlet生命周期的分析,DispatcherServlet应该也存在init方法,通过查找,我们可以在其父类HttpServletBean找到init方法,那么本篇的分析就从这里开始吧。
2.HttpServletBean初始化
/** * DispatcherServlet 初始化入口 * Map config parameters onto bean properties of this servlet, and * invoke subclass initialization. * @throws ServletException if bean properties are invalid (or required * properties are missing), or if subclass initialization fails. */ @Override public final void init() throws ServletException { // Set bean properties from init parameters. /** * 1.加载初始化参数,如: * <servlet> * <servlet-name>example</servlet-name> * <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> * <init-param> * <param-name>name</param-name> * <param-value>jack</param-value> * </init-param> * <load-on-startup>1</load-on-startup> * </servlet> * 这里会解析init-param列表。 */ PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // Let subclasses do whatever initialization they like. // 2.留给子类覆盖的模板方法 initServletBean(); }
该方法最主要的作用就是初始化init-param,如果我们没有配置任何init-param,那么该方法不会执行任何操作。从这里我们没有拿到有用的信息,但是在该方法结尾有initServletBean(),这是一个模板方法,可以由子类来实现,那么接下来我们就去看其子类FrameworkServlet中的initServletBean吧。
3.FrameworkServlet初始化
protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'"); if (logger.isInfoEnabled()) { logger.info("Initializing Servlet '" + getServletName() + "'"); } long startTime = System.currentTimeMillis(); try { // 为当前servlet初始化web应用上下文 this.webApplicationContext = initWebApplicationContext(); // 空的模板方法 initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed", ex); throw ex; } if (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"; logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value); } if (logger.isInfoEnabled()) { logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); } }
protected WebApplicationContext initWebApplicationContext() { // 获取rootContext,该Context就是通过ContextLoaderListener创建的XmlWebApplicationContext WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; // 如果当前webApplicationContext不为null,则为其设置父容器 if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } // 未能通过构造函数注入,则尝试去ServletContext容器中查找有无WebApplicationContext if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } // 以上均无WebApplicationContext,则创建一个新的WebApplicationContext if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } // 刷新上下文容器,空的模板方法,留给子类实现 if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. onRefresh(wac); } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
代码读到这里的时候,大家一定心存疑问,在加载ContextLoaderListener的时候不是已经创建过一次XmlWebApplicationContext了么?为什么这里又要创建一次呢?通过下面的图来看一下:
在这里插入图片描述
Spring中的 ApplicationContext 实例是可以有范围( scope) 的。在Spring MVC中,每个 DispatcherServlet 都持有一个自己的上下文对象 WebApplicationContext ,它又继承了根( root) WebApplicationContext 对象中已经定义的所有bean。这些继承的bean可以在具体的Servlet实例中被重载,在每个Servlet实例中你也可以定义其scope下的新bean。
DispatcherServlet 的初始化过程中,Spring MVC会在你web应用的 WEB-INF 目录下查找一个名为[servlet-name]-servlet.xml的配置文件,并创建其中所定义的bean。如果在全局上下文中存在相同名字的bean,则它们将被新定义的同名bean覆盖。
看看下面这个 DispatcherServlet 的Servlet配置( 定义于web.xml文件中)
<servlet> <servlet-name>example</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>namespace</param-name> <param-value>example-servlet</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>example</servlet-name> <url-pattern>/example/*</url-pattern> </servlet-mapping>
有了以上的Servlet配置文件,你还需要在应用中的 /WEB-INF/ 路径下创建一个 example-servlet.xml 文件,在该文件中定义所有Spring MVC相关的组件( 比如bean等) 。你可以通过servlet初始化参数为这个配置文件指定其他的路径 。
当你的应用中只需要一个 DispatcherServlet 时,只配置一个根context对象也是可行的。
在这里插入图片描述
要配置一个唯一的根context对象,可以通过在servlet初始化参数中配置一个空的contextConfigLocation来做到,如下所示:
<web-app> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/root-context.xml</param-value> </context-param> <servlet> <servlet-name>dispatcher</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>dispatcher</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <listener> <listene-class>org.springframework.web.context.ContextLoaderListener</listene-class> </listener> </web-app>
我们继续来看initWebApplicationContext方法,根据Spring的注释,可以看到初始化的具体工作委托给了createWebApplicationContext方法。
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) { return createWebApplicationContext((ApplicationContext) parent); } protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } configureAndRefreshWebApplicationContext(wac); return wac; }
该方法与上一节的逻辑大致相同,这里不多赘述了。为当前Servlet创建完WebApplicationContext之后,紧接着就要调用其onRefresh方法,在FrameworkServlet中,该方法是一个空的模板方法,其具体的实现留给了子类。那么接下来就要去分析DispatcherServlet的onRefresh方法了。
4.DispatcherServlet初始化
在了解DispatcherServlet之前,先回顾一下DispatcherServlet的内置组件及其作用。
bean的类型 | 作用 |
HandlerMapping | 处理器映射。它会根据某些规则将进入容器的请求映射到具体的处理器以及一系列前处理器和后处理器( 即处理器拦截器) 上。具体的规则视 HandlerMapping 类的实现不同而有所不同。其最常用的一个实现支持你在控制器上添加注解,配置请求路径。当然,也存在其他的实现。 |
HandlerAdapter | 处理器适配器。拿到请求所对应的处理器后,适配器将负责去调用该处理器,这使得 DispatcherServlet 无需关心具体的调用细节。比方说,要调用的是一个基于注解配置的控制器,那么调用前还需要从许多注解中解析出一些相应的信息。因此, HandlerAdapter 的主要任务就是对 DispatcherServlet 屏蔽这些具体的细节。 |
HandlerExceptionResolver | 处理器异常解析器。它负责将捕获的异常映射到不同的视图上去,此外还支持更复杂的异常处理代码。 |
ViewResolver | 视图解析器。它负责将一个代表逻辑视图名的字符串( String) 映射到实际的视图类型 View 上。 |
LocaleResolver&LocaleContextResolver | 地区解析器 和 地区上下文解析器。它们负责解析客户端所在的地区信息甚至时区信息,为国际化的视图定制提供了支持。 |
ThemeResolver | 主题解析器。它负责解析你web应用中可用的主题,比如,提供一些个性化定制的布局等。 |
MultipartResolver | 解析multi-part的传输请求,比如支持通过HTML表单进行的文件上传等。 |
FlashMapManager | FlashMap管理器。它能够存储并取回两次请求之间的 FlashMap 对象。后者可用于在请求之间传递数据,通常是在请求重定向的情境下使用。 |
来看具体代码:
protected void onRefresh(ApplicationContext context) { initStrategies(context); } protected void initStrategies(ApplicationContext context) { // 1.初始化MultipartResolver initMultipartResolver(context); // 2.初始化LocaleResolver initLocaleResolver(context); // 3.初始化ThemeResolver initThemeResolver(context); // 4.初始化HandlerMappings initHandlerMappings(context); // 5.初始化HandlerAdapters initHandlerAdapters(context); // 6.初始化HandlerExceptionResolvers initHandlerExceptionResolvers(context); // 7.初始化RequestToViewNameTranslator initRequestToViewNameTranslator(context); // 8.初始化ViewResolvers initViewResolvers(context); // 9.初始化FlashMapManager initFlashMapManager(context); // 以上,其中4、5、6、8步的初始化逻辑是相同的 }
4.1 初始化MultipartResolver
private void initMultipartResolver(ApplicationContext context) { try { this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); if (logger.isTraceEnabled()) { logger.trace("Detected " + this.multipartResolver); } else if (logger.isDebugEnabled()) { logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName()); } } catch (NoSuchBeanDefinitionException ex) { // Default is no multipart resolver. this.multipartResolver = null; if (logger.isTraceEnabled()) { logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared"); } } }
如果配置文件中有multipartResolver,则会将配置的multipartResolver进行实例化。
4.2 初始化LocaleResolver
private void initLocaleResolver(ApplicationContext context) { try { this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); if (logger.isTraceEnabled()) { logger.trace("Detected " + this.localeResolver); } else if (logger.isDebugEnabled()) { logger.debug("Detected " + this.localeResolver.getClass().getSimpleName()); } } catch (NoSuchBeanDefinitionException ex) { // We need to use the default. this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); if (logger.isTraceEnabled()) { logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME + "': using default [" + this.localeResolver.getClass().getSimpleName() + "]"); } } }
如果配置文件中有localeResolver,则会将配置的localeResolver进行实例化。
4.3 初始化ThemeResolver
private void initThemeResolver(ApplicationContext context) { try { this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class); if (logger.isTraceEnabled()) { logger.trace("Detected " + this.themeResolver); } else if (logger.isDebugEnabled()) { logger.debug("Detected " + this.themeResolver.getClass().getSimpleName()); } } catch (NoSuchBeanDefinitionException ex) { // We need to use the default. this.themeResolver = getDefaultStrategy(context, ThemeResolver.class); if (logger.isTraceEnabled()) { logger.trace("No ThemeResolver '" + THEME_RESOLVER_BEAN_NAME + "': using default [" + this.themeResolver.getClass().getSimpleName() + "]"); } } }
如果配置文件中有themeResolver,则会将配置的themeResolver进行实例化。
4.4 初始化HandlerMappings
/** * 实例化HandlerMappings,如果没有自定义HandlerMappings,则默认使用BeanNameUrlHandlerMapping * Initialize the HandlerMappings used by this class. * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace, * we default to BeanNameUrlHandlerMapping. */ private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // 1.如果希望获取所有的HandlerMapping,包括父容器。 // detectAllHandlerMappings默认为true 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()); // 排序 // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); } } // 2.否则只获取当前上下文自定义配置的handlerMapping else { try { HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default HandlerMapping later. } } // 3.上述两步都未能获取到handlerMapping,则使用默认的handlerMapping, // 包括BeanNameUrlHandlerMapping和RequestMappingHandlerMapping // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }
这里要注意一下this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
,Spring是如何来加载默认的handlerMapping的。在getDefaultStrategies方法中有defaultStrategies静态变量,该变量通过下面的静态代码块来初始化:
static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); } }
该代码加载了DispatcherServlet.properties配置文件,并对其进行了解析和存储。打开该配置文件:
# Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.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.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
这里存储了DispatcherServlet一些默认内置组件的实现类,从这里可以看到Spring默认使用的HandlerMapping为BeanNameUrlHandlerMapping和RequestMappingHandlerMapping。
4.5 初始化HandlerAdapters
private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; if (this.detectAllHandlerAdapters) { // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts. Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<>(matchingBeans.values()); // We keep HandlerAdapters in sorted order. AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { try { 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. } } // Ensure we have at least some HandlerAdapters, by registering // default HandlerAdapters if no other adapters are found. 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"); } } }
与初始化HandlerMappings逻辑大致相同,不多赘述。
4.6 初始化HandlerExceptionResolvers
private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; 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()); // We keep HandlerExceptionResolvers in sorted order. AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); } } else { try { 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. } } // Ensure we have at least some HandlerExceptionResolvers, by registering // default HandlerExceptionResolvers if no other resolvers are found. 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"); } } }
与初始化HandlerMappings逻辑大致相同,不多赘述。
4.7初始化RequestToViewNameTranslator
private void initRequestToViewNameTranslator(ApplicationContext context) { try { 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) { // We need to use the default. 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() + "]"); } } }
4.8初始化ViewResolvers
private void initViewResolvers(ApplicationContext context) { this.viewResolvers = null; 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()); // We keep ViewResolvers in sorted order. AnnotationAwareOrderComparator.sort(this.viewResolvers); } } else { try { 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. } } // Ensure we have at least one ViewResolver, by registering // a default ViewResolver if no other resolvers are found. 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"); } } }
与初始化HandlerMappings逻辑大致相同,不多赘述。
4.9 初始化FlashMapManager
private void initFlashMapManager(ApplicationContext context) { try { 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) { // We need to use the default. this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class); if (logger.isTraceEnabled()) { logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME + "': using default [" + this.flashMapManager.getClass().getSimpleName() + "]"); } } }
到这里DispatcherServlet初始化的工作就基本完成了。