SpringServletContainerInitializer
参考之前的博文无配置文件SSM
那么容器启动时候,为什么这么MyWebAppInitializer
这个类会被加载呢,进而创建根容器,创建web的ioc容器呢
来看这个类的介绍:
WebApplicationInitializer是Spring MVC提供的一个接口,它确保检测到您的实现并自动用于初始化Servlet 3容器。WebApplicationInitializer的抽象基类实现AbstractDispatcherServletInitializer通过重写方法来指定servlet映射和DispatcherServlet配置的位置,使得注册DispatcherServlet更加容易。
这里提到了初始化servlet3.0容器,那就有必要了解一下sevrvlet3.0的一个初始化规范,根据官方文档的解释,用自己的话总结一下有关的重要几点,大概意思就是:
web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer,加载这个文件制定的类,并且可以通过@HandlesTypes注解,把感兴趣的类的信息,注入到
onStartup(Set<Class<?>> var1, ServletContext var2)
var1当中。
经过查看源码,以及debug调试发现,这个ServletContainerInitializer其实就是SpringServletContainerInitializer
,来看SpringServletContainerInitializer所在在包下的目录结构。
根据上面介绍的servlet3.0规范,当servlet容器启动的时候,就会加载javax.servlet.ServletContainerInitializer文件中指定的类
其内容就是org.springframework.web.SpringServletContainerInitializer
,所有也就会在容器启动的时候,加载SpringServletContainerInitializer
SpringServletContainerInitializer代码如下:
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
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.add((WebApplicationInitializer) 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);
//遍历容器中所有的WebApplicationInitializer#onStartup方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
这段代码的简单的介绍一下:因为这个类上标注了@HandlesTypes(WebApplicationInitializer.class)
,所以会加载所有WebApplicationInitializer
信息都会被注入到onStartup()
方法的形参webAppInitializerClasses
上,然后遍历,判断如果不是接口【!waiClass.isInterface()】,不是抽象类【!Modifier.isAbstract(waiClass.getModifiers()】,那么就实例化,并且添加在集合当中。最后遍历集合initializers
,代用每一个对象的#onStartup(servletContext)
方法。在这个遍历上,打上一个断点,看一下initializers
中有哪些对象。
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
查看结果如下:(加载的感兴趣的类信息一共有4个,因为其他三个都是抽象类,不符合实例化的条件,所有集合中也就一个类,就是我们定义的MyWebAppInitializer
)
经过上面的分析,可以大概小结一下:
因为servlet3.0容器加载规范,会加载特定位置的文件中指定的类,在这里也就是SpringServletContainerInitializer,,然后加载@HandleType注解标注的感兴趣的类,然后根据条件实例化这些类,添加到集合中,遍历集合然后调用他们的onStartup方法
有了这些基础,那么就可以来看一下,他的执行调用过程,来看一下容器如何被创建的,核心控制器DispatcherServlet是如何被添加到容器中…
执行initializer.onStartup(servletContext)
,所以来到MyWebAppInitializer#onStartup
的方法,因为他本身没有重写这个方法,所以往上找他的父类AbstractAnnotationConfigDispatcherServletInitializer
,但是这里也没有那么就接着再找父类AbstractDispatcherServletInitializer
,在这个类中onStartup
方法代码如下:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
他分为两步,先执行super.onStartup(servletContext)
,所以接着来到他的父类AbstractContextLoaderInitializer
中这个方法的实现,
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
//调用#createRootApplicationContext()方法,因为自己没有实现,调用子类的方法
//执行AbstractAnnotationConfigDispatcherServletInitializer这个类的createRootApplicationContext()
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
//添加容器监听事件
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
进入AbstractAnnotationConfigDispatcherServletInitializer
@Override
protected WebApplicationContext createRootApplicationContext() {
//getRootConfigClasses()方法,这个类自己没有实现,调用的其实是MyWebAppInitializer#getRootConfigClasses方法
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
//创建一个根容器
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
//把我们的配置类注册进去
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
执行到这里,super.onStartup(servletContext),方法执行完,根容器已经被创建出来了,接着调用registerDispatcherServlet(servletContext)这个方法,方法如下:
protected void registerDispatcherServlet(ServletContext servletContext) {
//获得名字 默认是dispatcher
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
// 因为本类没有实现,调动子类AbstractAnnotationConfigDispatcherServletInitializer的
//createServletApplicationContext方法,创建出web的ioc容器 这个方法的代码与createRootApplicationContext()的执行过程类似
/*调用子类MyWebAppInitializer 的getServletConfigClasses方法,加载AppConfig.class 然后创建出一个容器。
*/
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
//其实就是new DispatcherServlet(servletAppContext),创建出一个DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 将创建出来的DispatcherServlet添加到容器中
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
//容器加载,就创建
registration.setLoadOnStartup(1);
//设置拦截的mapping 回调子类的getServletMappings()方法 也就是MyWebAppInitializer#getServletMappings方法
registration.addMapping(getServletMappings());
//设置异步支持 默认是true
registration.setAsyncSupported(isAsyncSupported());
//添加过滤器,可以通过重写ServletFilters方法 ,来添加过滤器
Filter[] filters = get ServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
执行到这里,作用是创建出web的ioc容器,并且创建出DispatcherServlet,设置了他的启动机制,设置了ServletMapping,所有就不需要的web.xml配置,就能成功启动容器。
(其实根容器和web容器其实是一个父子关系),每一个容器,装载一些特定的组件。
DispatcherServlet需要一个WebApplicationContext(一个普通ApplicationContext的扩展)来进行自己的配置。WebApplicationContext有一个指向它关联的ServletContext和Servlet的链接。它还绑定到ServletContext,以便应用程序可以在requestcontext tutils上使用静态方法来查找需要访问的WebApplicationContext。
对于许多只有一个WebApplicationContext的应用程序来说,这是简单而充分的。还可以有一个上下文层次结构,其中一个根WebApplicationContext在多个DispatcherServlet(或其他Servlet)实例之间共享,每个实例都有自己的子WebApplicationContext配置。有关上下文层次结构特性的更多信息,
根WebApplicationContext通常包含基础设施bean,比如需要跨多个Servlet实例共享的数据存储库和业务服务。这些bean是有效继承的,可以在特定于Servlet的子WebApplicationContext中重写(即重新声明),该上下文通常包含给定Servlet的本地
总结一下,画了一个uml时序图:
- web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
- spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件;
- 并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)