在 servlet 中有一个规范,就是当 servlet 容器启动的时候会根据 spi 规范加载META-INF/services 文件夹下面的 javax.servlet.ServletContainerInitializer 文件,该文件下面的类会实现 javax.servlet.ServletContainerInitializer 接口。

    spring-web jar 包下面META-INF.services 下有个javax.servlet.ServletContainerInitializer文件

    打开之后显示内容:

    org.springframework.web.SpringServletContainerInitializer


    该类在tomcat启动的时候会被 servlet 容器反射实例化,然后调用SpringServletContainerInitializer类的 onStartup 方法,并且 servlet 容器会收集实现了@HandlesTypes 注解里面的接口的类,并且做为入参传入到 onStartup 方法中.
    我们拿到 set 容器中的类就可以反射调用接口里面的方法了,这是 servlet 规范,该规范就能保证 servlet 容器在启动的时候就会完成这些操作。Springmvc 就借助这一点完成了取代web.xml 的工作。



    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext)
    throws ServletException {

    List initializers = new LinkedList<>();

    if (webAppInitializerClasses != null) {
    for (Class<?> waiClass : webAppInitializerClasses) {
    // Be defensive: Some servlet containers provide us with invalid classes,
    // no matter what @HandlesTypes says…
    if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
    WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
    try {
    //通过反射实例化,加入到initializers容器里面
    initializers.add((WebApplicationInitializer)
    ReflectionUtils.accessibleConstructor(waiClass).newInstance());
    }
    catch (Throwable ex) {
    throw new ServletException(“Failed to instantiate WebApplicationInitializer class”, ex);
    }
    }
    }
    }

    if (initializers.isEmpty()) {
    servletContext.log(“No Spring WebApplicationInitializer types detected on classpath”);
    return;
    }

    servletContext.log(initializers.size() + “ Spring WebApplicationInitializers detected on classpath”);
    AnnotationAwareOrderComparator.sort(initializers);
    for (WebApplicationInitializer initializer : initializers) {
    initializer.onStartup(servletContext); //initializer容器挨个调用onStartup
    }
    }

    }

    org.springframework.web.SpringServletContainerInitializer#onStartup 调用
    org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup 在里面实例化父类的onStartup方法,注册了
    org.springframework.web.context.AbstractContextLoaderInitializer#registerContextLoaderListener Spring上下文监听器
    然后把ContextLoaderListener加入到ServletContext里面


    然后在这个方法内部循环调用所有实现了WebApplicationInitializer接口的onStartup方法.总共有三个:

    org.springframework.web.context.AbstractContextLoaderInitializer#onStartup

    org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup
    这个方法注册了ContextLoaderListener和DispatcherServlet

    org.springframework.web.server.adapter.AbstractReactiveWebInitializer#onStartup









    收集的是实现了 WebApplicationInitializer 接口的类,在 springmvc 工程中我们自己写了这么一个类,如图:
    取代web.xml配置 - 图1
    该类的父类最终会实现 WebApplicationInitializer,所以该类的父类必定会有一个onStartup 方法。其父类截图如下:

    取代web.xml配置 - 图2
    1、super.onStartup 完成了实例化 listener 的工作
    取代web.xml配置 - 图3

    这些代码功能就类似于在 web.xml 配置了 ContextLoaderListener,做了几个事情,1 创建了上下文对象,如图:
    取代web.xml配置 - 图4
    这个上下文对象就是基于注解扫描的上下文对象,所以用这个上下文是需要注册一个类进
    去,这个类就是用钩子方法调用到了自己写的方法
    取代web.xml配置 - 图5
    在钩子方法中获取到的类 springContainer 就会去扫描基本包,有@ComponentScan 注解,如
    图:
    取代web.xml配置 - 图6

    通过钩子方法获取到扫描类后,注册到了上下文对象中,然后把 spring 的上下文对象设置到
    了 ContextLoaderListener 监听器对象中,最后把监听器对象设置到了 servletContext 中。这
    里上下文对象还没有调用 refresh 方法完成 spring 的启动。




    2、registerDispatcherServlet(servletContext);完成了实例化 DispatcherServlet


    取代web.xml配置 - 图7
    步骤跟创建监听器差不多,创建上下文对象,跟上面差不多,创建 dispatcherServlet
    对象,把 servlet 对象加入到 servletContext 上下文中。把上下文对象设置到了
    dispatcherServlet 对象中了,这里上下文对象还没有调用 refresh 方法,没有启动
    spring 容器。



    3、启动 spring 容器
    监听器的启动
    取代web.xml配置 - 图8

    其实没什么特别的,就是会拿到上下文对象调用 refresh 方法,需要特殊记忆的就是,会
    把上下文对象设置到 servletContext 中。
    取代web.xml配置 - 图9

    DispatcherServlet 的启动
    因为这个是一个 servlet,servlet 要完成 spring 容器的启动,就只能在 init 方法里面做。

    取代web.xml配置 - 图10

    取代web.xml配置 - 图11


    这里也没什么特别的,也是拿到上下文对象调用了refresh方法完成spring容器的启动。
    需要注意的是这里:
    取代web.xml配置 - 图12
    会从 servletContext 中获取父容器

    取代web.xml配置 - 图13

    就是由 listener 负责启动的容器,然后把父容器设置到了自己的上下文对象中,所以这
    里监听器启动的容器是父容器,dispatcherServlet 启动的容器是子容器,两者是父子关系.
    这里就用 servlet 规范完成了取代 web.xml 的工作,并启动了容器。