1.1 前言

我们都知道DispatcherServlet是前端控制器,是整个Spring Mvc的入口,但是这个前端控制器里面又有很多箱子,每一个箱子都有其独有的功能,当我们翻开一个箱子之后看看里面有什么的时候,又会发现箱子里面装着又一个箱子,所以我们需要一个个的探究这些箱子。

2.1 DispatcherServlet 初始化过程

2.1.1 配置 DispatcherServlet

  • 首先,Tomcat每次启动时都会加载并解析/WEB-INF/web.xml文件,所以可以先从web.xml找突破口,主要代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

<!-- 初始化参数 需要告诉Spring配置文件的位置 -->
<init-param >
<param-name >contextConfigLocation</param-name>
<param-value >classpath:/applicationContext.xml</param-value>
</init-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet >
<servlet-name >spring-mvc</servlet-name>
<!-- servlet类 -->
<servlet-class >org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 初始化参数 -->
<init-param >
<param-name >contextConfigLocation</param-name>
<param-value >classpath:/spring-mvc.xml</param-value>
</init-param>
<!-- 启动时加载 -->
<load-on-startup >1</load-on-startup>
</servlet>
<servlet-mapping >
<servlet-name >spring-mvc</servlet-name>
<url-pattern >/</url-pattern>
</servlet-mapping>

2.1.1.1 ContextLoaderListener

  • 先来看下面的配置文件,这里配置了Spring的配置文件路径及一个监听类ContextLoaderListener
1
2
3
4
5
6
7
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value >classpath:/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  • 我们知道在是使用Spring的时候通常是使用ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");的方式来获取Spring容器的,现在我们是要将Spring容器Web环境联系起来,所以需要通过这个类ContextLoaderListener将两者联系起来,那么它是怎么实现联系的呢?

  • 查看ContextLoaderListener类,可以看到它实现了ServletContextListener这个接口,这个是属于Servlet的类,如果实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法contextInitialized()contextDestroyed()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// org.springframework.web.context.ContextLoaderListener
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

public ContextLoaderListener() {}

public ContextLoaderListener(WebApplicationContext context) {
super(context);
}

/**
* ServletContext启动之后会调用此方法
*
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}


/**
* ServletContext关闭之后会调用此方法
*
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}

}

// javax.servlet.ServletContextListener
public interface ServletContextListener extends EventListener {

public void contextInitialized(ServletContextEvent sce);

public void contextDestroyed(ServletContextEvent sce);
}
  • ContextLoaderListener类继承关系
  • 1、ServletContext启动之后会调用此方法contextInitialized(ServletContextEvent event)

    • 进入此方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
      // web.xml中存在多次ContextLoader定义抛异常
      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!");
      }

      Log logger = LogFactory.getLog(ContextLoader.class);
      servletContext.log("Initializing Spring root WebApplicationContext");
      if (logger.isInfoEnabled()) {
      logger.info("Root WebApplicationContext: initialization started");
      }
      long startTime = System.currentTimeMillis();

      try {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      if (this.context == null) {
      // 创建WebApplicationContext
      this.context = createWebApplicationContext(servletContext);
      }
      if (this.context instanceof ConfigurableWebApplicationContext) {
      ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
      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 ->
      // determine parent for root web application context, if any.
      ApplicationContext parent = loadParentContext(servletContext);
      cwac.setParent(parent);
      }
      // 构造Spring容器
      configureAndRefreshWebApplicationContext(cwac, servletContext);
      }
      }
      // 将WebApplicationContext记录在servletContext中
      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
      currentContextPerThread.put(ccl, this.context);
      }

      if (logger.isDebugEnabled()) {
      logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
      WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
      }
      if (logger.isInfoEnabled()) {
      long elapsedTime = System.currentTimeMillis() - startTime;
      logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
      }

      return this.context;
      }
      catch (RuntimeException ex) {
      logger.error("Context initialization failed", ex);
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
      throw ex;
      }
      catch (Error err) {
      logger.error("Context initialization failed", err);
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
      throw err;
      }
      }
    • 可以看到上面主要逻辑分为以下几个步骤

      • 1、创建WebApplicationContext对象

        • 代码
          1
          2
          3
          4
          5
          6
          7
          8
          protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
          Class<?> contextClass = determineContextClass(sc);
          if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
          throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
          "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
          }
          return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
          }
      • 2、构造Spring容器,这个很重要

        • 进入configureAndRefreshWebApplicationContext(cwac, servletContext);,可以看到主要逻辑是读取了我们上面配置的contextConfigLocation配置路径,然后执行了wac.refresh();方法,这个方法是构造Spring容器的核心方法
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
          if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
          // The application context id is still set to its original default value
          // -> assign a more useful id based on available information
          String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
          if (idParam != null) {
          wac.setId(idParam);
          }
          else {
          // Generate default id...
          wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
          ObjectUtils.getDisplayString(sc.getContextPath()));
          }
          }
          // 在Spring也存一份ServletContext
          wac.setServletContext(sc);
          /**
          * 读取contextConfigLocation配置路径
          * <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value >classpath:/applicationContext.xml</param-value>
          </context-param>
          */
          String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
          if (configLocationParam != null) {
          wac.setConfigLocation(configLocationParam);
          }

          // The wac environment's #initPropertySources will be called in any case when the context
          // is refreshed; do it eagerly here to ensure servlet property sources are in place for
          // use in any post-processing or initialization that occurs below prior to #refresh
          ConfigurableEnvironment env = wac.getEnvironment();
          if (env instanceof ConfigurableWebEnvironment) {
          ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
          }

          customizeContext(sc, wac);
          // 核心 refresh()方法
          wac.refresh();
          }
      • 3、构建完成之后将此对象放在servletContext中,这样就可以在Web服务中使用Spring容器了,开发者能够在客户端请求提供服务之前向ServletContext中添加任意的对象,ServletContextweb容器运行期间都是可见的

        • 代码
          1
          servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
      • 4、映射当前的类加载器与创建的实例到全局变量中currentContextPerThread

        • 代码
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          /**
          * Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext.
          */
          private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
          new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);

          /**
          * The 'current' WebApplicationContext, if the ContextLoader class is
          * deployed in the web app ClassLoader itself.
          */
          private static volatile WebApplicationContext currentContext;

          ...

          ClassLoader ccl = Thread.currentThread().getContextClassLoader();
          if (ccl == ContextLoader.class.getClassLoader()) {
          currentContext = this.context;
          }
          else if (ccl != null) {
          // 映射当前的类加载器与创建的实例到全局变量中currentContextPerThread
          currentContextPerThread.put(ccl, this.context);
          }
  • 2、ServletContext关闭之后会调用此方法contextDestroyed(ServletContextEvent event)

    • 这里的逻辑主要是销毁Spring容器及清理Web容器
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      @Override
      public void contextDestroyed(ServletContextEvent event) {
      closeWebApplicationContext(event.getServletContext());
      ContextCleanupListener.cleanupAttributes(event.getServletContext());
      }
      public void closeWebApplicationContext(ServletContext servletContext) {
      servletContext.log("Closing Spring root WebApplicationContext");
      try {
      if (this.context instanceof ConfigurableWebApplicationContext) {
      ((ConfigurableWebApplicationContext) this.context).close();
      }
      }
      finally {
      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      if (ccl == ContextLoader.class.getClassLoader()) {
      currentContext = null;
      }
      else if (ccl != null) {
      currentContextPerThread.remove(ccl);
      }
      servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
      if (this.parentContextRef != null) {
      this.parentContextRef.release();
      }
      }
      }

2.1.1.2 DispatcherServlet

  • 上面通过ContextLoaderListener已经将Spring容器构建起来了并与Web容器联系起来了,但Spring MVC的核心逻辑是在DispatcherServlet中进行的,这就是下面web.xml配置的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<servlet >
<servlet-name >spring-mvc</servlet-name>
<!-- servlet类 -->
<servlet-class >org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 初始化参数 -->
<init-param >
<param-name >contextConfigLocation</param-name>
<param-value >classpath:/spring-mvc.xml</param-value>
</init-param>
<!-- 启动时加载 -->
<load-on-startup >1</load-on-startup>
</servlet>
<servlet-mapping >
<servlet-name >spring-mvc</servlet-name>
<url-pattern >/</url-pattern>
</servlet-mapping>
  • 可以看到 DispatcherServlet 本身就是个Servletservlet的生命周期是由servlet容器来控制的,可分为以下几个阶段:
    • 1、初始化阶段: 先加载servlet类并构建,然后执行其init()方法进行初始化,这个步骤只会执行一次
    • 2、运行阶段:当servlet容器收到一个请求时,会创建servletRequestservletResponse对象,并其将作为参数调用service()方法,执行业务逻辑
    • 3、销毁阶段:销毁当然是销毁servlet,释放servlet所占用的资源,比如关闭数据库连接,关闭文件输入输出类等等
  • 上面回顾了一些servlet的一些知识,那么DispatcherServlet也是围绕这几个阶段来处理请求的,下面看看DispatcherServlet做了什么呢,我们先看下DispatcherServlet的继承关系,重点关注HttpServletBeanFrameworkServlet 这两个类
1
2
3
4
5
GenericServlet (javax.servlet)
HttpServlet (javax.servlet.http)
HttpServletBean (org.springframework.web.servlet)
FrameworkServlet (org.springframework.web.servlet)
DispatcherServlet (org.springframework.web.servlet)
  • 通过上图可以看到 DispatcherServlet 实现了SpringApplicationContextAwareEnvironmentCapableEnvironmentAware这些Spring中的接口,XXXAwareSpring中表示对XXX可以感知,通俗点可以说在某个类中想使用Spring的一些东西,就可以实现XXXAware接口告诉Spring我要这个东西,比如ApplicationContextAware,该接口只有一个方法就是setApplicationContext(ApplicationContext applicationContext), 通过该方法可以得到ApplicationContextSpring容器会检测容器中的所有Bean,如果发现某个Bean实现了ApplicationContextAware接口,Spring容器会在创建该Bean之后,自动调用该BeansetApplicationContextAware()方法,调用该方法时,会将容器本身作为参数传给该方法——该方法中的实现部分将Spring传入的参数(容器本身)赋给该类对象的applicationContext实例变量,因此接下来可以通过该applicationContext实例变量来访问容器本身。实现XXXCapable接口表示可以得到某种能力,实现EnvironmentCapable接口说明可以得到Environment的能力,也就是可以提供Environment

    • ApplicationContextAware

      • 代码
        1
        2
        3
        4
        5
        6
        7
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
        if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
        this.webApplicationContext = (WebApplicationContext) applicationContext;
        this.webApplicationContextInjected = true;
        }
        }
    • EnvironmentCapable

      • 代码
        1
        2
        3
        4
        5
        6
        7
        @Override
        public ConfigurableEnvironment getEnvironment() {
        if (this.environment == null) {
        this.environment = createEnvironment();
        }
        return this.environment;
        }
2.1.1.2.1 父类HttpServletBean
  • HttpServletBean 覆写了GenericServletinit方法,此方法是第一次访问该DispatcherServlet的时候就会执行,对初始化过程做了一些处理,HttpServletBean 这个类的作用主要做一些初始化的工作,将web.xml中配置的参数设置到ServletConfig中。比如servlet标签的子标签init-param标签中配置的参数(classpath:/spring-mvc.xml),第二个就是调用FrameworkServlet子类方法构建及发布WebApplicationContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}

// 构造过程中会使用ServletConfig对象找出web.xml配置文件中的配置参数 比如web.xml配置的<init-param >
// 并设置到ServletConfig
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 使用BeanWrapper构造DispatcherServlet BeanWrapper是Spring提供用来操作JavaBean属性的工具,使用它可以直接修改一个对象的属性
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// 模板方法
initBeanWrapper(bw);
// 设置DispatcherServlet属性
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 触发子类{@link FrameworkServlet#initServletBean() } 用构建及发布WebApplicationContext
initServletBean();

if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
2.1.1.2.2 父类FrameworkServlet
  • 关注initServletBean()方法,该方法的实现在FrameworkServlet 类里,这个类的作用是将ServletSpring容器上下文关联。其实也就是初始化FrameworkServlet的属性webApplicationContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@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 {
// 初始化 WebApplicationContext (即SpringMVC的IOC容器)
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (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");
}
}
  • 进入this.webApplicationContext = initWebApplicationContext();方法,这个方法的作用是先得到根上下文rootContext 然后创建webApplicationContext并设置根上下文(将 Spring 的容器设为 SpringMVC 容器的父容器),这里的容器涉及到两个,一个是Spring父容器,另一个是SpringMVC 容器,所以需要把这两个容器合并,最后就是发布这个整合版的 WebApplicationContext 容器到 ServletContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
// 获取ContextLoaderListener 初始化并注册在 ServletContext 中的Spring 的容器容器,这里是父容器
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
// 将 Spring 的容器设为 SpringMVC 容器的父容器
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
// 如果 WebApplicationContext 为空,则进行查找,能找到说明上下文已经在别处初始化。
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 如果 WebApplicationContext 仍为空,则以 Spring 的容器为父上下文建立一个新的,并设置根上下文为父上下文
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.
// 模版方法,由 DispatcherServlet 实现
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
// 发布这个 WebApplicationContext 容器到 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;
}
  • Spring父容器

    • 回顾上面的配置文件的配置,这里就是指定了Spring父容器的配置

      1
      2
      3
      4
      <init-param >
      <param-name >contextConfigLocation</param-name>
      <param-value >classpath:/applicationContext.xml</param-value>
      </init-param>
    • 父容器的构建关注WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());方法,这里是就是获取我们上面在ContextLoaderListener 初始化并注册在 ServletContextWebApplicationContext

      • 进入此方法,可以看到是直接从ServletContext中获取sc.getAttribute(attrName);

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
         public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
        return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        }
        public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
        Assert.notNull(sc, "ServletContext must not be null");
        // 从ServletContext中获取
        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;
        }
      • 这里的WebApplicationContext的值是什么时候弄进去的呢,回顾一下ContextLoaderListenerinitWebApplicationContext()方法,下面第三步servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);就把WebApplicationContext赋值到了ServletContext

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

        ...

        try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        if (this.context == null) {
        // 1、创建WebApplicationContext
        this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
        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 ->
        // determine parent for root web application context, if any.
        ApplicationContext parent = loadParentContext(servletContext);
        cwac.setParent(parent);
        }
        // 2、构造Spring容器
        configureAndRefreshWebApplicationContext(cwac, servletContext);
        }
        }
        // 3、将WebApplicationContext记录在servletContext中
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        ...
  • SpringMVC 容器

    • 回顾上面的配置文件的配置,这里就是指定了SpringMVC父容器的配置

      1
      2
      3
      4
      <init-param >
      <param-name >contextConfigLocation</param-name>
      <param-value >classpath:/spring-mvc.xml</param-value>
      </init-param>
    • SpringMVC 容器的构建关注wac = createWebApplicationContext(rootContext);方法,这里是以 Spring父容器为基础构建一个新的SpringMVC 容器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
       	protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
      return createWebApplicationContext((ApplicationContext) parent);
      }
      protected WebApplicationContext createWebApplicationContext(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);

      wac.setEnvironment(getEnvironment());
      wac.setParent(parent);
      /**
      * 这里配置了ConfigLocation
      *
      * <init-param >
      * <param-name >contextConfigLocation</param-name>
      * <param-value >classpath:/spring-mvc.xml</param-value>
      * </init-param>
      */
      wac.setConfigLocation(getContextConfigLocation());

      configureAndRefreshWebApplicationContext(wac);

      return wac;
      }
  • 上面已经得到了SpringMVC 的容器,继续走下面的逻辑
1
2
3
4
5
6
7
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.
// 刷新Spring MVC,模版方法,由 DispatcherServlet 实现
onRefresh(wac);
}
  • 进入onRefresh(wac);DispatcherServlet 实现,这里的操作就是Spring Mvc自身的初始化过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
// initStrategies方法内部会初始化各个策略接口的实现类。
initStrategies(context);
}

/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 上传组件初始化,注册后每个请求会检查是否包含multipart
initMultipartResolver(context);
// 国际化组件初始化
initLocaleResolver(context);
// 主题解析器初始化
initThemeResolver(context);
// 核心:请求映射处理组件初始化
initHandlerMappings(context);
// 处理适配器组建初始化
initHandlerAdapters(context);
// 异常处理组件初始化
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
// 视图处理组件初始化
initViewResolvers(context);
initFlashMapManager(context);
}
  • 到这里SpringMVC 的容器已经初始完成了,那么就需要发布这个容器了,可以看到还是设置到ServletContext中,这样的话就可以在整个Web环境中使用该容器了
1
2
3
4
5
6
7
8
9
10
if (this.publishContext) {
// Publish the context as a servlet context attribute.
// 发布这个 WebApplicationContext 容器到 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 + "]");
}
}

2.1.1 DispatcherServlet 默认加载bean

  • DispatcherServlet 初始化的时候会默认加载一些组件,代码如下,可以看到是在static代码块中读取一个配置文件并把它注册为Properties defaultStrategies对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * Name of the class path resource (relative to the DispatcherServlet class)
    * that defines DispatcherServlet's default strategy names.
    */
    private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

    ...

    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文件,该文件在Spring web mvc 包下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # 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.annotation.DefaultAnnotationHandlerMapping

    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    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 配置的是一些类的全限定名,那它是在哪里调用的呢,还是回到之前的onRefresh(wac);initStrategies(ApplicationContext context)方法

2.1.2 自定义标签解析

  • 如果需要使用Spring则需要添加上面的标签用于开启Spring MVC的功能,可以知道这个标签是属于Spring的自定义标签,所以找到对应的命名空间处理器NamespaceHandler

  • 可以定位到MvcNamespaceHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MvcNamespaceHandler extends NamespaceHandlerSupport {

@Override
public void init() {
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
}

}
  • 关注registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); 进入到AnnotationDrivenBeanDefinitionParserparse方法,可以看到主要逻辑就是构造各种BeanDefinition并注册到容器中,可以发现有我们最常用RequestMappingHandlerMappingRequestMappingHandlerAdapterDefaultHandlerExceptionResolver等等,这些beanSpring MVC的重要组成部分,具体功能在以后的章节将会介绍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
XmlReaderContext readerContext = parserContext.getReaderContext();

CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
parserContext.pushContainingComponent(compDefinition);

RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);

// 注册 RequestMappingHandlerMapping
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("order", 0);
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);

if (element.hasAttribute("enable-matrix-variables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
else if (element.hasAttribute("enableMatrixVariables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}

configurePathMatchingProperties(handlerMappingDef, element, parserContext);
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);

RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);

RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
RuntimeBeanReference validator = getValidator(element, source, parserContext);
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);

// 注册 ConfigurableWebBindingInitializer
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
bindingDef.setSource(source);
bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
bindingDef.getPropertyValues().add("conversionService", conversionService);
bindingDef.getPropertyValues().add("validator", validator);
bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);
String asyncTimeout = getAsyncTimeout(element);
RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);

// 注册 RequestMappingHandlerAdapter
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
addRequestBodyAdvice(handlerAdapterDef);
addResponseBodyAdvice(handlerAdapterDef);

if (element.hasAttribute("ignore-default-model-on-redirect")) {
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
}
else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
// "ignoreDefaultModelOnRedirect" spelling is deprecated
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
}

if (argumentResolvers != null) {
handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
if (asyncTimeout != null) {
handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
}
if (asyncExecutor != null) {
handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
}

handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef);

String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
// 注册 CompositeUriComponentsContributorFactoryBean
RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
uriCompContribDef.setSource(source);
uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
readerContext.getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef);

// 注册 ConversionServiceExposingInterceptor
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
csInterceptorDef.setSource(source);
csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);

// 注册 MappedInterceptor
RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedCsInterceptorDef.setSource(source);
mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedCsInterceptorDef);

// 注册 ExceptionHandlerExceptionResolver
RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
exceptionHandlerExceptionResolver.setSource(source);
exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(exceptionHandlerExceptionResolver);

if (argumentResolvers != null) {
exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}

String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver);

// 注册 ResponseStatusExceptionResolver
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
responseStatusExceptionResolver.setSource(source);
responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
responseStatusExceptionResolver.getPropertyValues().add("order", 1);
String responseStatusExceptionResolverName =
readerContext.registerWithGeneratedName(responseStatusExceptionResolver);

// 注册 DefaultHandlerExceptionResolver
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExceptionResolverName =
readerContext.registerWithGeneratedName(defaultExceptionResolver);

parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));

// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);

parserContext.popAndRegisterContainingComponent();

return null;
}

3.1 总结

  • HttpServletBean 继承了HttpServlet,所以主要逻辑还是执行其init()进行Servlet的初始化任务,主要有下面逻辑

    • 1、解析参数配置:将web.xml中配置的参数设置到ServletConfig中。比如servlet标签的子标签init-param标签中配置的参数。
    • 2、触发子类{@link FrameworkServlet#initServletBean() }方法 用与构建及发布WebApplicationContext
  • FrameworkServletServletSpring容器上下文关联。其实也就是初始化FrameworkServlet的属性webApplicationContext,这个属性代表SpringMVC容器,它有个父类上下文,既web.xml中配置的ContextLoaderListener监听器初始化的容器上下文。

  • DispatcherServlet 初始化各个功能的实现类。比如文件上传处理、异常处理、视图处理、请求映射处理等。

  • DispatcherServlet会自动注册一些特殊的Bean,无需我们注册,如果我们注册了,默认的将不会注册。 因此BeanNameUrlHandlerMapping、SimpleControllerHandlerAdapter是不需要注册的,DispatcherServlet默认会注册这两个Bean

4.1 参考

官方文档: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html

http://www.cnblogs.com/fangjian0423/p/springMVC-dispatcherServlet.html

https://blog.csdn.net/lang_programmer/article/details/71598042