Spring mvc项目上下文启动流程解析

来源:互联网 发布:linux合并两个文件夹 编辑:程序博客网 时间:2024/05/14 12:56

软件开发的中,如果某些特性的使用比较普遍,那么这些特性往往可以作为平台特性来实现,通过对这些平台特性进行有效的封装,使其向其他应用开放。正是如此,spring由于其IOC、AOP、事务处理、持久化驱动等特点,使得其起到了一个应用平台的作用。Spring MVC是Spring的一个重要的模块,其web应用的实现,是由Spring的来支撑的,Spring MVC的是实现也是依托再Spring平台提供的基础特性的。本文主要是介绍Spring mvc容器初始化的过程,从中可以看出Spring MVC的对于Spring的依赖。

一、从Web应用角度看Spring MVC

在Servlet模型中,请求-响应的实现依赖于两大元素的共同配合:

1. 配置Servlet及其映射关系(在web.xml中) 
2. 在Servlet实现类中完成响应逻辑

项目规模扩大之后,请求-响应的映射关系全部定义在web.xml中,将造成web.xml的不断膨胀而变得难以维护。针对这个问题,SpringMVC提出的方案就是:提炼一个核心的Servlet覆盖对所有Http请求的处理。这一被提炼出来的Servlet,通常被我们称之为:核心分发器。在SpringMVC中,核心分发器就是org.springframework.web.servlet.DispatcherServlet

核心分发器要解决的是下面两个问题:

问题1:核心Servlet应该能够建立起一整套完整的对所有Http请求进行规范化处理的流程。

问题2:核心Servlet应该能够根据一定的规则对不同的Http请求分发到不同的Servlet对象上去进行处理。

针对上面的这个两个问题,SpringMVC的解决方案是:将整个处理流程规范化,并把每一个处理步骤分派到不同的组件中进行处理。 

处理流程规范化 :将处理流程划分为若干个步骤(任务),并使用一条明确的逻辑主线将所有的步骤串联起来
处理流程组件化 : 将处理流程中的每一个步骤(任务)都定义为接口,并为每个接口赋予不同的实现模式
 

处理流程规范化的首要内容就是考虑一个通用的Servlet响应程序大致应该包含的逻辑步骤: 

  • 对Http请求进行初步处理,查找与之对应的Controller处理类(方法)
  • 调用相应的Controller处理类(方法)完成业务逻辑
  • 对Controller处理类(方法)调用时可能发生的异常进行处理
  • 根据Controller处理类(方法)的调用结果,进行Http响应处理

所谓的组件化,实际上也就是使用编程语言将这些逻辑语义表达出来。在Java语言中,最适合表达逻辑处理语义的语法结构是接口,而接口可以有不同的实现,因此上述的四个流程也就被定义为了四个不同接口,它们分别是: 

  • HandlerMapping
  • HandlerAdapter
  • HandlerExceptionResolver
  • ViewResolver

二、从Spring角度看Spring MVC

从上面可以看出,组件是核心分发器(DispatchServlet)的核心所在,它们是http请求处理的逻辑载体,DispatcherServlet是逻辑处理的调度中心,组件则是被调度的操作对象。而Spring容器在这里所起到的作用,是协助DispatcherServlet更好地对组件进行管理

我们知道,SpringMVC的组件是一个个的接口定义,当我们在SpringMVC的核心配置文件中定义一个组件时,使用的却是组件的实现类,用具体的实现类来指定组件的行为模式,不同的实现类代表了不同的行为模式,它们在Spring中是可以共存的。Spring容器对这些实现类进行管理,具体如何使用,由应用程序本身来决定。


上图是Spring官方reference中的一幅图,DispatchServlet对外接收http的请求,而请求的处理的是依靠组件来完成的,组件的接口实现的是依靠Spring IOC容器(WebApplicationContext)来管理。从这个图中我们可以看出,Spring MVC实现web应用是依赖与Spring提供的基础特性(IOC等)。图中的两个WebApplicationContext的区别,留到下面再讲。

三、Spring MVC 入口配置文件web.xml

Spring mvc 有哪些配置文件:

  1. 入口配置文件:web.xml;由web或应用服务器为每个web项目加载的配置文件。
  2. 应用上下文:包括web框架特有配置文件:SpringMVC的${dispatcherServletName}-servlet.xml配置文件。和Spring的配置文件applicationContext.xml,applicationContext-*.xml。 

遵循servlet规范,Spring MVC的web应用的入口配置文件也是web.xml。web容器的初始化首先是加载web.xml文件,Jetty在启动时首先加载此配置文件,并且对其中定义的listener和servlet等进行相应的加载和初始化。Jetty并不清楚(也不关心)其他配置文件的存在,因此,加载其他配置文件应该是你(框架)的事情。那么怎么加载呢?前面说过Jetty在启动时会自动加载和初始化listener和servlet,那么我们可以自定义一个listener(ContextLoaderListener)或servlet(DispatcherServlet),Jetty会根据web.xml加载和初始化他们,而他们则负责加载相应的配置文件。

在web.xml配置文件中,有两个主要的配置:ContextLoaderListener和DispatcherServlet。同样的关于spring配置文件的相关配置也有两部分:context-param和DispatcherServlet中的init-param。那么,这两部分的分别表示是Spring 容器的初始化和web容器的初始化。DispatcherServlet和ContextLoaderListener提供了在Web容器中对spring的接口。ServletContext为Spring的IOC容器提供了一个宿主环境,在宿主环境中,Spring MVC建立起了一个IOC容器体系。

  如果项目中不使用webapplicationconext,可以不用配置contextLoadListener进行监听。

下面看一下Spring mvc 中web.xml文件的相关配置内容:

 

[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"  
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">  
  5.     <display-name>push-center-server</display-name>  
  6.     <description><span style="font-family: Arial, sans-serif;">test</span></description>  
  7.     <context-param>  
  8.         <param-name>webAppRootKey</param-name>  
  9.         <param-value>test</param-value>  
  10.     </context-param>  
  11.     <context-param>  
  12.         <param-name>contextConfigLocation</param-name>  
  13.         <param-value>  
  14.             classpath:applicationContext.xml  
  15.         </param-value>  
  16.     </context-param>  
  17.   
  18.   
  19.     <context-param>  
  20.         <param-name>log4jConfigLocation</param-name>  
  21.         <param-value>classpath:log4j2.xml</param-value>  
  22.     </context-param>  
  23.   
  24.   
  25.     <!-- 项目使用的配置文件位置.项目启动自动读取 -->  
  26.     <context-param>  
  27.         <param-name>propertiesConfigLocation</param-name>  
  28.         <param-value>/WEB-INF/conf/config.properties</param-value>  
  29.     </context-param>  
  30.   
  31.   
  32.     <!--jmonitor-->  
  33.     <context-param>  
  34.         <param-name>jmonitor-configfile</param-name>  
  35.         <param-value>jmonitor.properties</param-value>  
  36.     </context-param>  
  37.     <listener>  
  38.         <listener-class>com.meituan.jmonitor.servlet.ContextListener</listener-class>  
  39.     </listener>  
  40.   
  41.   
  42.     <listener>  
  43.         <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
  44.     </listener>  
  45.   
  46.   
  47.     <listener>  
  48.         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  49.     </listener>  
  50.   
  51.   
  52.   
  53.   
  54.     <listener>  
  55.         <listener-class>  
  56.             org.springframework.web.context.request.RequestContextListener  
  57.         </listener-class>  
  58.     </listener>  
  59.   
  60.   
  61.   
  62.   
  63.     <!--jmonitor http collector-->  
  64.     <filter>  
  65.         <filter-name>JMonitorHttpMonitorFilter</filter-name>  
  66.         <filter-class>com.meituan.jmonitor.collector.http.HttpMonitorFilter</filter-class>  
  67.     </filter>  
  68.     <filter-mapping>  
  69.         <filter-name>JMonitorHttpMonitorFilter</filter-name>  
  70.         <url-pattern>/*</url-pattern>  
  71.     </filter-mapping>  
  72.   
  73.   
  74.     <servlet>  
  75.         <servlet-name>appServlet</servlet-name>  
  76.         <servlet-class>com.sankuai.meituan.web.MtDispatcherServlet</servlet-class>  
  77.         <init-param>  
  78.             <param-name>contextConfigLocation</param-name>  
  79.             <param-value>classpath:webmvc-config.xml</param-value>  
  80.         </init-param>  
  81.         <load-on-startup>2</load-on-startup>  
  82.     </servlet>  
  83.   
  84.   
  85.     <servlet-mapping>  
  86.         <servlet-name>appServlet</servlet-name>  
  87.         <url-pattern>/</url-pattern>  
  88.     </servlet-mapping>  
  89.   
  90.   
  91. </web-app>  
  92.    


四、Spring IOC容器(根上下文)的初始化

 Spring Framework本身没有Web功能,Spring MVC使用WebApplicationContext接口扩展ApplicationContext,使得拥有web功能,WebApplicationContext接口默认的实现是XmlWebApplicationContext。那么,Spring MVC是如何在web环境中创建IoC容器呢?

先看一下WebApplicationContext的源码,

WebApplicationContext

[java] view plain copy
  1. public interface WebApplicationContext extends ApplicationContext {  
  2.     //用于在ServletContext中存取根上下文  
  3.     String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";  
  4.        
  5.     String SCOPE_REQUEST = "request";  
  6.        
  7.     String SCOPE_SESSION = "session";  
  8.        
  9.     String SCOPE_GLOBAL_SESSION = "globalSession";  
  10.        
  11.     String SCOPE_APPLICATION = "application";  
  12.        
  13.     String SERVLET_CONTEXT_BEAN_NAME = "servletContext";  
  14.        
  15.     String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";  
  16.        
  17.     String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";  
  18.    
  19.     //取得当前web容器的ServletContext  
  20.     ServletContext getServletContext();  
  21. }  

 

      在Spring MVC中,Spring Context是以父子的继承结构存在的。Web环境(ServletContext)中存在一个根上下文,这个Context是整个应用的根上下文,是其他context的双亲Context。同时Spring MVC也对应的持有一个独立的Context,它是根上下文的子上下文。

由ContextLoaderListener首先启动的上下文为根上下文,该上下文是与ServletContext相伴而生的,在根上下文的基础上,Spring MVC对应持有的一个用来管理控制器需要的对象的子上下文。下图是ContextLoaderListener继承关系,它实现了ServletContextListener接口,这个接口提供了与Servlet生命周期结合的回调,比如contextInitialized和contextDestroyed方法。建立WebApplicationContext的过程是在contextInitialized的接口实现中完成的,具体的载入IOC容器的过程是由ContextLoader来完成的。


 ContextLoaderListener

[java] view plain copy
  1. public class ContextLoaderListener extends ContextLoader implements ServletContextListener {  
  2.     private ContextLoader contextLoader;  
  3.    
  4.     public ContextLoaderListener() {  
  5.     }  
  6.        
  7.     public ContextLoaderListener(WebApplicationContext context) {  
  8.         super(context);  
  9.     }  
  10.    
  11.     public void contextInitialized(ServletContextEvent event) {  
  12.         this.contextLoader = createContextLoader();  
  13.         if (this.contextLoader == null) {  
  14.             this.contextLoader = this;  
  15.         }  
  16.         this.contextLoader.initWebApplicationContext(event.getServletContext());  
  17.     }  
  18.        
  19.     @Deprecated  
  20.     protected ContextLoader createContextLoader() {  
  21.         return null;  
  22.     }  
  23.        
  24.     @Deprecated  
  25.     public ContextLoader getContextLoader() {  
  26.         return this.contextLoader;  
  27.     }  
  28.    
  29.        
  30.     public void contextDestroyed(ServletContextEvent event) {  
  31.         if (this.contextLoader != null) {  
  32.             this.contextLoader.closeWebApplicationContext(event.getServletContext());  
  33.         }  
  34.         ContextCleanupListener.cleanupAttributes(event.getServletContext());  
  35.     }  

从ContextLoaderListener源码可以看出,实现的是ServletContextListener接口,这个接口里的函数会结合Web容器的生命周期被调用。因为ServletContextListener是ServletContext的监听者,当servletContext启动或者停止的时候,会触发响应的事件,监听器ServletContextListener会接收到事件,会做出相应的响应,比如这里的contextInitialized方法和contextDestroyed方法。Spring IOC容器(根上下文)的生成与销毁就是通过这个两个方法的,所以根上下文是与ServletContext相伴而生的。

所以当Web应用启动时,contextInitialized方法会执行载入根上下文(IOC容器),具体过程是首先从Servlet事件中得到ServletContext,然后以ServletContext为宿主环境,载入根上下文(IOC容器),具体的载入过程是由ContextLoader处理的。

下图所示为根上下文的加载过程,下面将结合源码来看一下这个过程是如何实现的。

 

根上下文的载入过程:


      ROOT Context是在ContextLoaderListener中配置的,ContextLoaderListener读取context-param中的contextConfigLocation指定的配置文件,创建ROOT Context。下面看一下ContextLoaderListener中创建context的源码:

ContextLoader加载根上下文的源码

[java] view plain copy
  1. //这里开始对WebApplicationContext进行初始化  
  2. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {  
  3.     
  4. //在整个web应用中,只能有一个根上下文,判断ServletContext中是否已经有根上下文  
  5.         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {  
  6.             throw new IllegalStateException(  
  7.                     "Cannot initialize context because there is already a root application context present - " +  
  8.                     "check whether you have multiple ContextLoader* definitions in your web.xml!");  
  9.         }  
  10.         Log logger = LogFactory.getLog(ContextLoader.class);  
  11.         servletContext.log("Initializing Spring root WebApplicationContext");  
  12.         if (logger.isInfoEnabled()) {  
  13.             logger.info("Root WebApplicationContext: initialization started");  
  14.         }  
  15.         long startTime = System.currentTimeMillis();  
  16.         try {  
  17.             // Store context in local instance variable, to guarantee that  
  18.             // it is available on ServletContext shutdown.  
  19.             if (this.context == null) {  
  20. // 执行了创建WebApplicationContext的操作  
  21.                 this.context = createWebApplicationContext(servletContext);  
  22.             }  
  23.             if (this.context instanceof ConfigurableWebApplicationContext) {  
  24.                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;  
  25.                 if (!cwac.isActive()) {  
  26.                     // The context has not yet been refreshed -> provide services such as  
  27.                     // setting the parent context, setting the application context id, etc  
  28.                     if (cwac.getParent() == null) {  
  29.                         // The context instance was injected without an explicit parent ->  
  30.                         // determine parent for root web application context, if any.  
  31. //载入根上下文的双亲上下文  
  32.                         ApplicationContext parent = loadParentContext(servletContext);  
  33.                         cwac.setParent(parent);  
  34.                     }  
  35.                     configureAndRefreshWebApplicationContext(cwac, servletContext);  
  36.                 }  
  37.             }  
  38.     
  39. //将根上下文放置在servletContext中  
  40.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);  
  41.             ClassLoader ccl = Thread.currentThread().getContextClassLoader();  
  42.             if (ccl == ContextLoader.class.getClassLoader()) {  
  43.                 currentContext = this.context;  
  44.             }  
  45.             else if (ccl != null) {  
  46.                 currentContextPerThread.put(ccl, this.context);  
  47.             }  
  48.             if (logger.isDebugEnabled()) {  
  49.                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +  
  50.                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");  
  51.             }  
  52.             if (logger.isInfoEnabled()) {  
  53.                 long elapsedTime = System.currentTimeMillis() - startTime;  
  54.                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");  
  55.             }  
  56.             return this.context;  
  57.         }  
  58.         catch (RuntimeException ex) {  
  59.             logger.error("Context initialization failed", ex);  
  60.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);  
  61.             throw ex;  
  62.         }  
  63.         catch (Error err) {  
  64.             logger.error("Context initialization failed", err);  
  65.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);  
  66.             throw err;  
  67.         }  
  68.     }  
  69.     
  70.     protected WebApplicationContext createWebApplicationContext(ServletContext sc) {  
  71. // 根据web.xml中的配置,决定用那种WebApplicationContext,默认用XmlWebApplicationContext  
  72.         Class<?> contextClass = determineContextClass(sc);  
  73. //判断contextClass是否继承ConfigurableWebApplicationContext或者是其接口实现  
  74.         if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {  
  75.             throw new ApplicationContextException("Custom context class [" + contextClass.getName() +  
  76.                     "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");  
  77.         }  
  78. //直接实例化需要产生的IOC容器  
  79.         return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);  
  80.     }  
  81.     
  82. //设置IOC容器各个参数,然后通过refresh启动容器的初始化  
  83. protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {  
  84. //设置application context ID  
  85.         if (ObjectUtils.identityToString(wac).equals(wac.getId())) {  
  86.             // The application context id is still set to its original default value  
  87.             // -> assign a more useful id based on available information  
  88.             String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);  
  89.             if (idParam != null) {  
  90.                 wac.setId(idParam);  
  91.             }  
  92.             else {  
  93.                 // Generate default id...  
  94.                 if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {  
  95.                     // Servlet <= 2.4: resort to name specified in web.xml, if any.  
  96.                     wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +  
  97.                             ObjectUtils.getDisplayString(sc.getServletContextName()));  
  98.                 }  
  99.                 else {  
  100.                     wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +  
  101.                             ObjectUtils.getDisplayString(sc.getContextPath()));  
  102.                 }  
  103.             }  
  104.         }  
  105. //设置ServletContext  
  106.         wac.setServletContext(sc);  
  107. //设置配置文件的位置参数  
  108.         String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);  
  109.         if (initParameter != null) {  
  110.             wac.setConfigLocation(initParameter);  
  111.         }  
  112. //在refresh之前,根据配置的config locations重新定制(Customize)ConfigurableWebApplicationContext  
  113.         customizeContext(sc, wac);  
  114. //启动容器的初始化  
  115.         wac.refresh();  
  116.     }  

从上面的源码中可以看出来,IOC容器在web容器中的启动过程,与在应用中启动IOC容器的方式相似,不同的是这里需要考虑web容器的环境的特点,比如各种参数的设置,IOC容器与web容器ServletContext的结合等。在初始化上下文以后,该上下文被存储再ServletContext中,这样就建立了一个全局的关于整个应用的上下文,即所谓的根上下文。同时在启动Spring MVC的时候,DispatchServlet在进行自己持有的上下文的初始化时,是将此根上下文设置为自己的双亲上下文的。

http://blog.csdn.net/and1kaney/article/details/51214193   这篇文章可以看到根容器初始化过程的整个的call hierarchy。

五、Spring MVC容器(子上下文)的初始化

以上是web容器中根上下文的加载与初始化,在完成对ContextLoaderListener的初始化以后,web容器开始初始化DispatchServlet,DispatchServlet会建立自己的上下文来管理Spring MVC的bean对象。在建立这个自己持有的上下文的时候,会从ServletContext中得到根上下文作为DispatchServlet持有的上下文的双亲上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文也保存到ServletContext中

我们先看下DispatchServlet的继承关系,如下图。DispatchServlet通过继承FrameworkServlet和HttpServletBean而继承了HttpServlet。HttpServletBean是Spring对于Servlet最低层次的抽象。在这一层抽象中,Spring会将这个Servlet视作是一个Spring的bean,并将web入口配置文件web.xml中DispatchServlet定义的init-param参数中的值作为bean的属性注入进来。

 

DispatcherServlet也是一个Servlet,根据Servlet规范的定义,Servlet中的两大核心方法init方法和service方法: 

1. init方法 

在整个系统启动时运行,且只运行一次。因此,在init方法中我们往往会对整个应用程序进行初始化操作。这些初始化操作可能包括对容器(WebApplicationContext)的初始化、组件和外部资源的初始化等等。 

2. service方法 

在整个系统运行的过程中处于侦听模式,侦听并处理所有的Web请求。因此,在service及其相关方法中,我们看到的则是对Http请求的处理流程。 

 

这篇文章主要是介绍Spring mvc 容器的初始化,所以主要是介绍iDisipatchServlet的init方法。



HttpServletBean

[java] view plain copy
  1.  @Override  
  2.     public final void init() throws ServletException {  
  3.         if (logger.isDebugEnabled()) {  
  4.             logger.debug("Initializing servlet '" + getServletName() + "'");  
  5.         }  
  6.         // Set bean properties from init parameters.  
  7.         try {  
  8. //读取web.xml中DispatchServlet定义中的<init-param>,对Bean属性进行配置  
  9.             PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);  
  10. //生成一个BeanWrapper,将当前的这个Servlet类转化为一个BeanWrapper,从而能够以Spring的方式来对init-param的值进行注入  
  11.             BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);  
  12.             ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());  
  13.             bw.registerCustomEditor(Resource.classnew ResourceEditor(resourceLoader, getEnvironment()));  
  14.             initBeanWrapper(bw);  
  15. //将init-param中的值注入  
  16.             bw.setPropertyValues(pvs, true);  
  17.         }  
  18.         catch (BeansException ex) {  
  19.             logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);  
  20.             throw ex;  
  21.         }  
  22.         // 调用子类的initServletBean进行具体的初始化  
  23.         initServletBean();  
  24.         if (logger.isDebugEnabled()) {  
  25.             logger.debug("Servlet '" + getServletName() + "' configured successfully");  
  26.         }  
  27.     }  

FrameworkServlet则是在HttpServletBean的基础之上的进一步抽象。通过FrameworkServlet真正初始化了一个Spring的容器(WebApplicationContext),并引入到Servlet对象之中: 

FrameworkServlet 

[java] view plain copy
  1. /** 
  2.      * Overridden method of {@link HttpServletBean}, invoked after any bean properties 
  3.      * have been set. Creates this servlet's WebApplicationContext. 
  4.      */  
  5.     @Override  
  6.     protected final void initServletBean() throws ServletException {  
  7.         getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");  
  8.         if (this.logger.isInfoEnabled()) {  
  9.             this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");  
  10.         }  
  11.         long startTime = System.currentTimeMillis();  
  12.         try {  
  13. //对Spring的容器(webApplicationContext)进行初始化  
  14.             this.webApplicationContext = initWebApplicationContext();  
  15.             initFrameworkServlet();  
  16.         }  
  17.         catch (ServletException ex) {  
  18.             this.logger.error("Context initialization failed", ex);  
  19.             throw ex;  
  20.         }  
  21.         catch (RuntimeException ex) {  
  22.             this.logger.error("Context initialization failed", ex);  
  23.             throw ex;  
  24.         }  
  25.         if (this.logger.isInfoEnabled()) {  
  26.             long elapsedTime = System.currentTimeMillis() - startTime;  
  27.             this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +  
  28.                     elapsedTime + " ms");  
  29.         }  
  30.     }  
  31.     
  32. protected WebApplicationContext initWebApplicationContext() {  
  33. //这里调用WebApplicationContextUtils静态类来从ServletContext中得到根上下文,使用这个根上下文作为当前MVC上下文的双亲上下文。  
  34.         WebApplicationContext rootContext =  
  35.                 WebApplicationContextUtils.getWebApplicationContext(getServletContext());  
  36.         WebApplicationContext wac = null;  
  37.         if (this.webApplicationContext != null) {  
  38.             // 如果一个context的实例被注入了,直接用  
  39.             wac = this.webApplicationContext;  
  40.             if (wac instanceof ConfigurableWebApplicationContext) {  
  41.                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;  
  42.                 if (!cwac.isActive()) {  
  43.                     // 如果此上下文还没有初始化,就设置上下文的参数,如双亲上下文、application context id等  
  44.                     if (cwac.getParent() == null) {  
  45.                         // 如果被注入的context实例没有双亲上下,则将根上下文设置为其双亲上下文  
  46.                         cwac.setParent(rootContext);  
  47.                     }  
  48. //设置其他参数,并启动容器初始化  
  49.                     configureAndRefreshWebApplicationContext(cwac);  
  50.                 }  
  51.             }  
  52.         }  
  53.         if (wac == null) {  
  54.             // No context instance was injected at construction time -> see if one  
  55.             // has been registered in the servlet context. If one exists, it is assumed  
  56.             // that the parent context (if any) has already been set and that the  
  57.             // user has performed any initialization such as setting the context id  
  58. //没有注入的context实例,这里从ServletContext中查找是否有context实例已经注册到了servlet context了  
  59.             wac = findWebApplicationContext();  
  60.         }  
  61.         if (wac == null) {  
  62.             // No context instance is defined for this servlet -> create a local one  
  63. //在ServletContext没有context实例,所以需要创建一个WebApplicationContext,以根上下文为双亲下文创建  
  64.             wac = createWebApplicationContext(rootContext);  
  65.         }  
  66. //刷新上下文(执行组件的初始化),这个方法由子类DispatchServlet的方法实现  
  67.         if (!this.refreshEventReceived) {  
  68.             // Either the context is not a ConfigurableApplicationContext with refresh  
  69.             // support or the context injected at construction time had already been  
  70.             // refreshed -> trigger initial onRefresh manually here.  
  71.             onRefresh(wac);  
  72.         }  
  73. //把当前的上下文存到ServletContext中去,使用的属性名是和当前的Servlet名相关的  
  74.         if (this.publishContext) {  
  75.             // Publish the context as a servlet context attribute.  
  76.             String attrName = getServletContextAttributeName();  
  77.             getServletContext().setAttribute(attrName, wac);  
  78.             if (this.logger.isDebugEnabled()) {  
  79.                 this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +  
  80.                         "' as ServletContext attribute with name [" + attrName + "]");  
  81.             }  
  82.         }  
  83.         return wac;  
  84.     }  
  85.     
  86. //创建上下文  
  87. protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {  
  88. // 根据web.xml中的配置,决定用那种WebApplicationContext,默认用XmlWebApplicationContext  
  89.         Class<?> contextClass = getContextClass();  
  90.         if (this.logger.isDebugEnabled()) {  
  91.             this.logger.debug("Servlet with name '" + getServletName() +  
  92.                     "' will try to create custom WebApplicationContext context of class '" +  
  93.                     contextClass.getName() + "'" + ", using parent context [" + parent + "]");  
  94.         }  
  95.         if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {  
  96.             throw new ApplicationContextException(  
  97.                     "Fatal initialization error in servlet with name '" + getServletName() +  
  98.                     "': custom WebApplicationContext class [" + contextClass.getName() +  
  99.                     "] is not of type ConfigurableWebApplicationContext");  
  100.         }  
  101.         ConfigurableWebApplicationContext wac =  
  102.                 (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);  
  103. //设置上下文,并启动初始化  
  104.         wac.setEnvironment(getEnvironment());  
  105.         wac.setParent(parent);  
  106.         wac.setConfigLocation(getContextConfigLocation());  
  107.         configureAndRefreshWebApplicationContext(wac);  
  108.         return wac;  
  109.     }  

在这个调用关系中,可以看到MVC的初始化是再DispatchServlet的initStrategies方法中完成的,包括对各种MVC框架的实现元素,比如支持国际化的LocaleResolver、支持request映射的HandlerMappings、以及视图生成的ViewResolver等的初始化。对于具体实现元素的初始化就不一一列出源码了,这里以HandlerMappings为例来说明MVC框架元素的初始化过程。

DispatchServlet

[java] view plain copy
  1. @Override  
  2.     protected void onRefresh(ApplicationContext context) {  
  3.         initStrategies(context);  
  4.     }  
  5.     /** 
  6.      * Initialize the strategy objects that this servlet uses. 
  7.      * <p>May be overridden in subclasses in order to initialize further strategy objects. 
  8.      */  
  9. ////初始化默认的Spring Web MVC框架使用的策略(如HandlerMapping)  
  10.     protected void initStrategies(ApplicationContext context) {  
  11.         initMultipartResolver(context);  
  12.         initLocaleResolver(context);  
  13.         initThemeResolver(context);  
  14.         initHandlerMappings(context);  
  15.         initHandlerAdapters(context);  
  16.         initHandlerExceptionResolvers(context);  
  17.         initRequestToViewNameTranslator(context);  
  18.         initViewResolvers(context);  
  19.         initFlashMapManager(context);  
  20.     }  
  21.     
  22. private void initHandlerMappings(ApplicationContext context) {  
  23.         this.handlerMappings = null;  
  24. //这里导入所有的HandlerMapping Bean,这些Bean可以是在当前的DispatchServlet的IOC容器,也可以是其双亲上下文中的,这里detectAllHandlerMappings默认是为true的,从所有的IOC容器中取  
  25.         if (this.detectAllHandlerMappings) {  
  26.             // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.  
  27.             Map<String, HandlerMapping> matchingBeans =  
  28.                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.classtruefalse);  
  29.             if (!matchingBeans.isEmpty()) {  
  30.                 this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());  
  31.                 // We keep HandlerMappings in sorted order.  
  32.                 OrderComparator.sort(this.handlerMappings);  
  33.             }  
  34.         }  
  35.         else {  
  36. //从当前IOC容器中通过getBean获取handlerMapping  
  37.             try {  
  38.                 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);  
  39.                 this.handlerMappings = Collections.singletonList(hm);  
  40.             }  
  41.             catch (NoSuchBeanDefinitionException ex) {  
  42.                 // Ignore, we'll add a default HandlerMapping later.  
  43.             }  
  44.         }  
  45. //如果没有找到handlerMappings,设置默认的handlerMapping,默认值设置在DispatcherServlet.properties中  
  46.         // Ensure we have at least one HandlerMapping, by registering  
  47.         // a default HandlerMapping if no other mappings are found.  
  48.         if (this.handlerMappings == null) {  
  49.             this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);  
  50.             if (logger.isDebugEnabled()) {  
  51.                 logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");  
  52.             }  
  53.         }  
  54.     }  

六、Spring MVC 上下初始化流程图