本节一起学习DispatcherServlet的启动和初始化
前面已经分析了Spring MVC的工作,下面就DispatcherServlet的启动和初始化进行详细的分析
作为Servlet,DispatcherServlet的启动和Servlet的启动过程是相关联的,在Servlet的初始化过程中,Servlet的init会被调用,进行初始化。下面看一下DispatcherServlet的基类HttpServletBean中的初始化过程。
@Override public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // 获取Servlet的初始化参数,对Bean属性进行配置 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; } } // 调用子类的initServletBean进行具体的初始化 initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }
在初始化开始时,要读取配置在ServletContext中的Bean属性参数,这些属性参数设置在web.xml的Web容器初始化参数中。使用编程式的方式设置这些Bean属性。接着会执行DispatcherServlet持有的IOC容器的初始化过程,在这个初始化过程中,一个新的上下文被建立起来,这个DispatcherServlet持有的上下文被设置为根上下文的子上下文。根上下文和Web应用相对应的一个上下文,而DispatcherServlet持有的上下文时和Servlet相对性的一个上下文。DispatcherServlet持有的上下文被建立起来后,也需要和其他IOC容器一样完成初始化,这个初始化也是通过refresh方法完成的。最后,DispatcherServlet给自己持有上下文命名,并把它设置到Web容器的上下文中,这个名词和web.xml设置的DispatcherServlet的Servlet的名称有关,从而保证这个上下文在web环境上下文体系中的唯一性。
@Override protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); //初始化上下文 try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }
protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());//得到根上下文 WebApplicationContext wac = 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); } } } 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(); } 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) { // 把当前建立的上下文存到ServletContext中 String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }
在这里MVC的上下文建立起来了,具体获取根上下文的过程在WebApplicationContextUtils中实现。
@Nullable public static WebApplicationContext getWebApplicationContext(ServletContext sc) { return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); }
@Nullable public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) { Assert.notNull(sc, "ServletContext must not be null"); Object attr = sc.getAttribute(attrName); if (attr == null) { return null; } if (attr instanceof RuntimeException) { throw (RuntimeException) attr; } if (attr instanceof Error) { throw (Error) attr; } if (attr instanceof Exception) { throw new IllegalStateException((Exception) attr); } if (!(attr instanceof WebApplicationContext)) { throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr); } return (WebApplicationContext) attr; }
这个根上下文是ContextLoader设置到ServletContext中的,使用的属性是ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,同时对这个IOC容器的Bean配置文件,ContextLoader也进行了设置,默认的位置在/WEB-INF/applicationContext.xml中。由于这个根上下文是DispatcherServlet建立的上下文的双亲上下文,所以跟上下文中管理的Bean也是可以被DispatcherServlet的上下文使用的。通过getBean向IOC容器获取Bean时,容器会先到它的双亲Ioc容器中获得getBean。
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) { return createWebApplicationContext((ApplicationContext) parent); }
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "'" + ", using parent context [" + parent + "]"); } 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); //使用反射实例化上下文,默认的XmlWebApplicationContext wac.setEnvironment(getEnvironment()); wac.setParent(parent);//设置双亲IOC String configLocation = getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); }//初始化 configureAndRefreshWebApplicationContext(wac); return wac; }这里创建上下文的过程与建立根上下文的过程很类似,建立DispatcherServlet的上下文需要把根上下文参数传递给它。然后使用反射技术实例化上下文对象,并为它设置参数。最后通过refresh完成IOC容器的最终初始化。
在Spring MVC DispatcherServlet的初始化过程中,以对HandlerMapping的初始化调用作为触发点,了解Spring MVC模块初始化的方法调用关系。这个调用关系最初是由HttpServletBean的init方法触发的,这个HttpServletBean是HttpServlet的子类,接着会在HttpServletBean的子类FrameworkServlet对IOC容器完成初始化,在初始化中,会调用DispatcherServlet的initStrategies方法,这个initStrategies方法,启动整个Spring MVC框架的初始化。
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
initStrategies完成了对各种MVC框架的实现元素,比如支持国际化的LocaleResolver、支持request映射的HandlerMappings以及视图生成的ViewResolvers等的初始化。
以HandlerMappings为例子,说明这个initHandlerMappings的过程,这里的Mapping关系的作用是,为Http请求找到相应的Controller控制器,从而利用这些控制器Controller完成设计好的数据处理工作。HandlerMappings完成对MVC中Controller的定义和配置,只不过在Web特定的应用环境中,这些控制器是与具体的HTTP请求相对应的。
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; //detectAllHandlerMappings默认为true,这里导入所有的HandlerMappings Bean,这些Bean可以在当前的DispatcherServlet的IOC容器中,也可能在双亲上下文中 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); } } else {//也可以根据名称从当前的IOC容器中通过getBean获得handlerMapping 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. } } // 如果没有找到handlerMapping,那么需要Servlet设定默认的handlerMappings if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }