Spring mvc 上下文初始化过程

在软件开发的中,如果某些特性的使用比较普遍,那么这些特性往往可以作为平台特性来实现,通过对这些平台特性进行有效的封装,使其向其他应用开放。正是如此,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容器体系。

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

 

 
  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>

 

 

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

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

先看一下WebApplicationContext的源码,

WebApplicationContext

 

 
  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

 

 
  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加载根上下文的源码

 

 
  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

 

 
  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.class, new 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 

 

 
  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

 

 
  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.class, true, false);

  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 上下初始化流程图

 

 

参考:

http://blog.csdn.net/c289054531/article/details/9196149

http://blog.csdn.net/prince2270/article/details/5889117

http://blog.arganzheng.me/posts/config-file-in-web-application.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值