一、从Servlet 3.0 规范说起

1.1 在Servlet 3.0 之前,我们都是通过XML的方式来进行Web三大组件的注册

  1. <servlet>
  2. <servlet-name>springmvc</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <init-param>
  5. <param-name>contextConfigLocation</param-name>
  6. <param-value>classpath*:config</param-value>
  7. </init-param>
  8. <load-on-startup>1</load-on-startup>
  9. </servlet>
  10. <servlet-mapping>
  11. <servlet-name>springmvc</servlet-name>
  12. <url-pattern>*.do</url-pattern>
  13. </servlet-mapping>
  14. <filter></filter>
  15. <listener></listener>

1.2 在Servlet3.0 规范之后,支持通过 @WebSerlvet,@WebFilter,@WebListener这种方式来注册三大组件

  1. @WebServlet
  2. public class MyServlet implements HttpServlet {
  3. public void doGet(HttpServletRequest request, HttpServletResponse response){
  4. response.getWriter().write("hello world");
  5. }
  6. }

1.3 除了通过注解的方式来注册三大组件之外,还支持,通过在META-INF/services 下,创建一个名为 javax.servlet.ServletContainerInitializer 的文件,文件内容为 ServletContainerInitializer 实现类的全限定类名

image.png

1.4 并且可以在这个实现类上添加@HandlesTypes 来指定感兴趣的类型,对应的实现类,将会传入到onStartup() 中的Set中

image.png

二、那么,在spring-mvc中,就实现了上述的操作,在org.springframework:spring-web.jar 中,就指定了SerlvetContainerInitializer的实现类: SpringServletContainerInitializer

2.1 紧接着我们就转向 SpringServletContainerInitializer ,这个类,标注了 HandlesTypes,指定了 类型为 WebApplicationInitializer

  1. @HandlesTypes(WebApplicationInitializer.class)
  2. public class SpringServletContainerInitializer implements ServletContainerInitializer {
  3. }

通过类的继承图谱可以看出,在WebApplicationInitializer 中,主要有三个子类(接口)

image.png
tomcat启动时,将会调用onStartup()方法

  1. 首先判断 不是接口,不是抽象类才能进来 !waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)
  2. 对其进行排序之后,循环调用WebApplicationInitializer的onStartup() 方法

2.2 分别看看这三个类都实现了什么东西

  1. 首先第一个类为 AbstractContextLoaderInitializer

在其onStartup()方法中调用了 registerContextLoaderListener(servletContext),从名字可以得出,这是注册Context-loader-listener的
分为三步:

  1. createRootApplicationContext() 由子类实现
  2. ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); 创建了ContextLoaderListener
  3. servletContext.addListener(listener); 将其注册到Servlet上下文中,将Serlvet上下文中有变化时,将会调用监听器的相应方法。
    1. 第一个类为 AbstractDispatcherServletInitializer
  4. super.onStartup(); 调用父类的onStartup(); 创建监听器以及父容器
  5. registerDispatcherServlet(servletContext); 创建DispatcherServlet,主要分为三步:
    1. createServletApplicationContext(); 创建子容器,由子类具体自己实现
    2. createDispatcherServlet(servletAppContext); 根据子容器,创建DispatcherServlet
      1. new DispatcherServlet(servletAppContext); 将子容器保存到 DispatcherServlet对象中
    3. servletContext.addServlet(servletName, dispatcherServlet); 第三步,将DispatcherServlet添加到Serlvet上下文中,默认的Serlvet名称为 dispatcher
  6. 然后设置一个Serlvet的启动参数
    1. registration.setLoadOnStartup(1); 设置启动级别
    2. registration.addMapping(getServletMappings()); 设置映射路径, getSerlvetMappings() 由子类实现
    3. registration.setAsyncSupported(isAsyncSupported()); 设置是否支持异步,默认为true
    4. 第三个类为 AbstractAnnotationConfigDispatcherServletInitializer

这个实现类中,没有重写onStartup() 方法,所以延照父类的onStartup()
这个类主要实现了 createRootApplicationContext() ,和createServletApplicationContext()

  1. createRootApplicationContext()
    1. getRootConfigClasses(); 获取 父容器的配置类,由具体的实现类进行指定
    2. AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); 创建一个父容器
    3. context.register(configClasses); 指定 父容器的配置类
    4. 注意,此时的容器中还没有任何组件,还没有调用refresh()
  2. createServletApplicationContext()
    1. AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); 创建一个子容器
    2. getServletConfigClasses(); 获取子容器的配置类
    3. context.register(configClasses); 指定 子容器的配置类
    4. 注意,此时的容器中还没有任何组件,还没有调用refresh()

三、当Serlvet上下文进行初始化时,将会调用 ContextLoaderListener 的 contextInitialized()

  1. initWebApplicationContext(event.getServletContext()); 初始化web容器
  2. cwac.isActive() 判断容器是否活着
  3. 活着的话 loadParentContext(servletContext),加载父容器(因为这是父容器,所以这个方法直接return null);
  4. cwac.setParent(parent); 设置父容器(设置了个null,设了个寂寞)
  5. configureAndRefreshWebApplicationContext(cwac, servletContext); 调用父容器的刷新方法
    1. wac.setId() 设置容器id
    2. wac.setServletContext(sc); 设置Servlet上下文
    3. wac.refresh(); 调用父容器进行刷新,此时getRootConfigClasses配置的配置类中的扫描组件,就在父容器当中了
  6. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 将父容器设置到Serlvet上下文中,以便后续子容器进行获取

四、在第一个访问DispatcherServlet的时候,将会执行Serlvet的生命周期方法

首先先看DispatcherServlet的继承图谱
image.png
在HttpServletBean中有Serlvet的生命周期方法(init() Servlet的初始化方法)
其中init最主要的逻辑在于调用 initServletBean();
这个方法在 FrameworkServlet 类中进行了重写
主要分为如下几步:

  1. initWebApplicationContext(); 初始化web容器
    1. WebApplicationContextUtils.getWebApplicationContext(getServletContext());
      1. getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 第一步,从Serlvet上下文中取出在三.6中设置到上下文中的父容器
      2. this.webApplicationContext != null, 判断子容器是否为空,在 2.2 的第二步中,已经将子容器传入,所以这里肯定不为null
      3. !cwac.isActive() 判断子容器是否活着
      4. cwac.setParent(rootContext); 活着,把子容器的parent属性设置为 从Servlet上下文中取出的父容器
      5. configureAndRefreshWebApplicationContext(cwac); 最后刷新 子容器,把getServletConfigClasses(); 中配置的扫描组件,扫描到了子组件中。
  2. initFrameworkServlet(); 初始化FrameworkServlet(空方法,不用管)

最后附上Spring 与SpringMVC的整合原理图

image.png

在FrameworkServlet中,有一个小坑,在SpringBoot 整合 web的时候,在DispatcherServletAutoConfiguration 中,

注入DispatcherServlet时,是通过空参传入的。

image.png
而FrameworkServlet实现了ApplicationContextAware 这个接口,这个接口,会在Spring容器启动的时候,将当前的ApplicationContext传入,执行setApplicationContext()
在FrameworkServlet中的实现如下,因为SpringBoot启动时是通通扫描,而没有像自定义的RootConfig一样,排除一些组件,所以,此时的DispatcherServlet这个组件是
被父容器扫描的,那么,此时传入的applicationContext就也是父容器,且父容器在web环境下,创建的是AnnotationConfigServletWebServerApplicationContext 这个类型,
这个类型实现了WebApplicationContext,所以会导致判断成立,那,在4大点中的 initWebApplicationContext,父子容器相同,会导致1.a.iii 的判断为false,
就没有了所谓的父子容器的概念。

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