在 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 throws ServletException { List 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 工程中我们自己写了这么一个类,如图:
该类的父类最终会实现 WebApplicationInitializer,所以该类的父类必定会有一个onStartup 方法。其父类截图如下:
1、super.onStartup 完成了实例化 listener 的工作
这些代码功能就类似于在 web.xml 配置了 ContextLoaderListener,做了几个事情,1 创建了上下文对象,如图:
这个上下文对象就是基于注解扫描的上下文对象,所以用这个上下文是需要注册一个类进
去,这个类就是用钩子方法调用到了自己写的方法
在钩子方法中获取到的类 springContainer 就会去扫描基本包,有@ComponentScan 注解,如
图:
通过钩子方法获取到扫描类后,注册到了上下文对象中,然后把 spring 的上下文对象设置到
了 ContextLoaderListener 监听器对象中,最后把监听器对象设置到了 servletContext 中。这
里上下文对象还没有调用 refresh 方法完成 spring 的启动。
2、registerDispatcherServlet(servletContext);完成了实例化 DispatcherServlet
步骤跟创建监听器差不多,创建上下文对象,跟上面差不多,创建 dispatcherServlet
对象,把 servlet 对象加入到 servletContext 上下文中。把上下文对象设置到了
dispatcherServlet 对象中了,这里上下文对象还没有调用 refresh 方法,没有启动
spring 容器。
3、启动 spring 容器
监听器的启动
其实没什么特别的,就是会拿到上下文对象调用 refresh 方法,需要特殊记忆的就是,会
把上下文对象设置到 servletContext 中。
DispatcherServlet 的启动
因为这个是一个 servlet,servlet 要完成 spring 容器的启动,就只能在 init 方法里面做。
这里也没什么特别的,也是拿到上下文对象调用了refresh方法完成spring容器的启动。
需要注意的是这里:
会从 servletContext 中获取父容器
就是由 listener 负责启动的容器,然后把父容器设置到了自己的上下文对象中,所以这
里监听器启动的容器是父容器,dispatcherServlet 启动的容器是子容器,两者是父子关系.
这里就用 servlet 规范完成了取代 web.xml 的工作,并启动了容器。