文章目录
目录
文章目录
前言
一、ServletContainerInitializer 接口是什么?
二、@HandlesTypes注解是什么
三、SpringServletContainerInitializer 是什么
四、WebApplicationInitializer 接口
总结
前言
本文不是SpringBoot的启动流程,是基于Tomcat,Spring,SpringMVC整合的纯注解启动流程,在我们启动Tomcat的时候Spring和SpringMvc是怎么加载启动的呢?ServletContainerInitializer 离不开这个关键的接口,围绕此接口在下文详细叙述。
一、ServletContainerInitializer 接口是什么?
在Tomcat容器启动的时候会扫描所有jar包中 meta-INF/services 目录下存不存在一个名为javax.servlet.ServletContainerInitializer的文件,此文件的内容是实现了ServletContainerInitializer接口的实现类的全类名, 若存在则tomcat容器会将该实现类加载和实例化并调用该类的onStartup方法
public interface ServletContainerInitializer {
void onStartup(Set> c, ServletContext ctx) throws ServletException;
}
可以看到onStartup方法有两个参数
- Set
> webAppInitializerClasses 是一个Class对象的Set集合,Set集合中的Class对象是从何而来呢?请带着问题向下接着看. -
ServletContext servletContex
解答刚才提出的疑问, 参数一 webAppInitializerClasses 从何而来@HandlesTypes(WebApplicationInitializer.class), tomcat容器会扫描所有实现了注解标注的WebApplicationInitializer.class这个接口的实现类, 并把所有实现类的Class对象在调用onStartup方法时以参数的形式传递过来
二、@HandlesTypes注解是什么
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
Class>[] value();
}
这个注解标注接受一个Class的数组,这个数组会被当作参数传递给onStartup方法, 对应此方法的第一个参数, 这就解答了上面的问题,Set集合中的Class对象是从何而来的
三、SpringServletContainerInitializer 是什么
本文不是SpringBoot的启动流程,是基于Tomcat,Spring,SpringMVC整合的纯注解启动流程,在我们启动Tomcat的时候Spring和SpringMvc是怎么加载启动的呢?ServletContainerInitializer 离不开这个关键的接口,围绕此接口在下文详细叙述。
一、ServletContainerInitializer 接口是什么?
在Tomcat容器启动的时候会扫描所有jar包中 meta-INF/services 目录下存不存在一个名为javax.servlet.ServletContainerInitializer的文件,此文件的内容是实现了ServletContainerInitializer接口的实现类的全类名, 若存在则tomcat容器会将该实现类加载和实例化并调用该类的onStartup方法
public interface ServletContainerInitializer {
void onStartup(Set> c, ServletContext ctx) throws ServletException;
}
可以看到onStartup方法有两个参数
- Set
> webAppInitializerClasses 是一个Class对象的Set集合,Set集合中的Class对象是从何而来呢?请带着问题向下接着看. ServletContext servletContex解答刚才提出的疑问, 参数一 webAppInitializerClasses 从何而来@HandlesTypes(WebApplicationInitializer.class), tomcat容器会扫描所有实现了注解标注的WebApplicationInitializer.class这个接口的实现类, 并把所有实现类的Class对象在调用onStartup方法时以参数的形式传递过来
二、@HandlesTypes注解是什么
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
Class>[] value();
}
这个注解标注接受一个Class的数组,这个数组会被当作参数传递给onStartup方法, 对应此方法的第一个参数, 这就解答了上面的问题,Set集合中的Class对象是从何而来的
三、SpringServletContainerInitializer 是什么
这个注解标注接受一个Class的数组,这个数组会被当作参数传递给onStartup方法, 对应此方法的第一个参数, 这就解答了上面的问题,Set集合中的Class对象是从何而来的
三、SpringServletContainerInitializer 是什么
在Spring中 (Spring的spi文件)org.springframework.web.SpringServletContainerInitializer 类就是实现了ServletContainerInitializer接口并重写了onStartup方法的实现类
这时,当tomcat启动时就会调用SpringServletContainerInitializer 类重写的onStartup方法, Spring重写的onStartup方法就做了三件事
- 见图片2号方框判断条件, 找出所有非接口, 非抽象类 且是WebApplicationInitializer.class的子类的Class对象,
- 见图片3号方框, 把满足条件的所有Class生成的对象, 添加到List集合存储起来
- 见图片4号方框, 遍历第二步的集合中的对象 调用对象的onStartup方法,注意此处的onStartup方法是WebApplicationInitializer.class 这个接口定义的
public interface WebApplicationInitializer { void onStartup(ServletContext servletContext) throws ServletException; }
四、WebApplicationInitializer 接口
方便以编程方式配置ServletContext, Spring和SpringMvc容器的启动也依赖此接口及其子类, 主要依赖的子类有三个
-
AbstractContextLoaderInitializer
-
AbstractDispatcherServletInitializer
-
AbstractAnnotationConfigDispatcherServletInitializer
类的继承结构如下:
当调用onStartup方法时由其子类AbstractDispatcherServletInitializer#onStartup方法实现
//此处代码在AbstractDispatcherServletInitializer类中
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//此处调用父类的AbstractContextLoaderInitializer#onStartup方法
super.onStartup(servletContext);//第一步
registerDispatcherServlet(servletContext);//第二步
}
第一步先调用了AbstractContextLoaderInitializer#onStartup方法, 调用了registerContextLoaderListener方法
此处代码在AbstractContextLoaderInitializer类中
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
//创建一个监听器, 在Tomcat容器初始化完成时调用, ContextLoaderListener同时继承了ContextLoader类
//创建ContextLoaderListener对象的同时 为ContextLoader的congtext属性赋值
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
//第一次设置 初始化容器时的 加载配置容器的类,接下来还有一次
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
}
createRootApplicationContext 方法创建了一个Spring空的容器对象,
//在AbstractAnnotationConfigDispatcherServletInitializer类中
protected WebApplicationContext createRootApplicationContext() {
//getRootConfigClasses是抽象方法需要子类实现,该方法的返回值会以FULL bean方式注册到SpringIoc容器中成为bean, 被SpringIOC容器感知接管
Class>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context ;
//创建注解驱动的IOC容器
context = new AnnotationConfigWebApplicationContext();
//以FULL 方式注册bean到IOC容器
context.register(configClasses);
return context;
}
else {
return null;
}
}
并为注册了一个监听器对象, 在Tomcat容器启动完成时调用 initWebApplicationContext方法初始化IOC容器,初始化IOC容器的关键方法是 configureAndRefreshWebApplicationContext方法 此方法主要是 激活IOC容器,加载bean到容器内, 并且在刷新容器之前 提供配置 IOC容器的方法,至此父子容器中 父容器SpringIOC容器创建并初始化完成.
//在ContextLoader类中
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//之前已经为this.context 赋值此if判断条件为false
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac;
cwac = (ConfigurableWebApplicationContext) this.context;
//判断当前容器是否激活
if (!cwac.isActive()) {
//获取当前容器父容器,此时容器为根容器即Spring容器,所以为null ,if条件为true
//SpringMvc子容器还未创建
if (cwac.getParent() == null) {
//此方法返回null
ApplicationContext parent = loadParentContext(servletContext);
//这时parent为null
cwac.setParent(parent);
}
//配置并刷新SpringIoc容器,在此方法之前IOC容器虽然创建但是是个空容器并没有bean
//此方法执行之后就会为激活IOC容器 ,并加载bean
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX
+ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
//容器刷新之前 第二次加载配置容器实现类并调用初始化方法
customizeContext(sc, wac);
wac.refresh();
}
第二步紧接着调用AbstractDispatcherServletInitializer#registerDispatcherServlet
//此处代码在AbstractDispatcherServletInitializer类中
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
WebApplicationContext servletAppContext = createServletApplicationContext();
frameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
registration.setLoadonStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}



