注:本文源码分析基于 tomcat 9.0.43,源码的gitee仓库仓库地址:gitee.com/funcy/tomca….
本文是tomcat源码分析的第三篇,本文来分析Context的启动流程。
上tomcat启动流程图:
可以看到, Context由Host启动,在Host#start(...)方法中,会调用Context#init(...)与Context#start(...)方法,不过Context#init(...)并没有做什么实质性的工作,我们重点关注Context#start(...).
在tomcat中,Context的实现为StandardContext,本文主要也是分析StandardContext的启动流程。
1. StandardContext#startInternal
要分析Context的启动流程,我们直接进入StandardContext#startInternal方法:
protected synchronized void startInternal() throws LifecycleException {
// 省略了一些代码
...
try {
if (ok) {
// 省略了一些代码
...
// 1. 触发 CONFIGURE_START 事件
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// 2. 启动 child, context 的 child 为 Wrapper
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
}
// 省略了一些代码
...
// 3. 处理 ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
// 执行 ServletContainerInitializers#onStartup(...)方法
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
// 4. 加载 Listener
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// 5. 加载 filter
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// 6. 处理 servlet,加载与初始化,Context 的 child 为 Wrapper,用来存放servlet的
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
} finally {
// 省略了一些代码
...
}
// 省略了一些代码
...
}
StandardContext#startInternal方法非常长,这里我们主要注意与servlet相关的操作,该方法处理流程如下:
- 触发
CONFIGURE_START事件,在这里会解析web.xml; - 启动
Context的子Container,也就是Wrapper,不过从代码来看,Wrapper初始化与启动时并没有做什么,就不分析了; - 处理
ServletContainerInitializers,也就是执行ServletContainerInitializers实现类的onStartup(...)方法; - 加载
servlet组件:Listener - 加载
servlet组件:filter - 加载
servlet组件:servlet
好了,接下来本文就逐一分析这些流程了。
2. 触发事件:LifecycleBase#fireLifecycleEvent(...)
我们来看看触发CONFIGURE_START事件的操作,代码为:
// 1. 触发 CONFIGURE_START 事件
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
我们跟下去:
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
for (LifecycleListener listener : lifecycleListeners) {
listener.lifecycleEvent(event);
}
}
这里会循环所有的LifecycleListener,逐一调用LifecycleListener#lifecycleEvent(...),需要注意的是,这些LifecycleListener是tomcat自身提供的,注意区别servlet提供的listener。
这个lifecycleListeners都有哪些呢?回忆下,我们在Demo01#test做了这么个操作:
public class Demo01 {
@Test
public void test() throws Exception {
...
// 这个 Context 就是 StandardContext
Context context = tomcat.addContext("", docBase);
// 得到 lifecycleListener 实际类型为 ContextConfig
LifecycleListener lifecycleListener = (LifecycleListener)
Class.forName(tomcat.getHost().getConfigClass())
.getDeclaredConstructor().newInstance();
// 将得到的 lifecycleListener 添加到context中
context.addLifecycleListener(lifecycleListener);
...
}
}
以上代码中,tomcat.addContext(...)得到的Context就是StandardContext,也就是本文我们要分析的Context,lifecycleListener得到的类型为ContextConfig,可以在StandardHost中看到:
private String configClass =
"org.apache.catalina.startup.ContextConfig";
所以,这段代码是向StandardContext中添加了一个LifecycleListener,类型为ContextConfig.
之所以要提这个LifecycleListener,是因为ContextConfig的作用十分重要!跟着代码的执行,我们进入ContextConfig#lifecycleEvent(...) 方法:
public void lifecycleEvent(LifecycleEvent event) {
// Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
// 处理启动事件
configureStart();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
// 处理启动前的事件
beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if (originalDocBase != null) {
context.setDocBase(originalDocBase);
}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
// 处理停止事件
configureStop();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
// 处理初始化事件
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
// 处理销毁事件
destroy();
}
}
可以看到,ContextConfig#lifecycleEvent(...)处理的事件非常多,不过我们主要关注CONFIGURE_START_EVENT,跟着ContextConfig#configureStart(...)一路执行,进入ContextConfig#webConfig(...)方法:
protected void webConfig() {
// 省略“解析 web.xml”的相关代码
...
// 查找 ServletContainerInitializer 的实现
if (ok) {
processServletContainerInitializers();
}
if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
// 会在这里处理 @HandlesTypes 注解
processClasses(webXml, orderedFragments);
}
// 省略“合并 web.xml”的代码
...
// 应用配置,将 webXml 中的配置信息应用用Context
configureContext(webXml);
// 省略“查找jar包中的静态资源”相关代码
...
// 将 ServletContainerInitializer 添加到Context
if (ok) {
for (Map.Entry<ServletContainerInitializer,
Set<Class<?>>> entry : initializerClassMap.entrySet()) {
if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(entry.getKey(), null);
} else {
context.addServletContainerInitializer(
entry.getKey(), entry.getValue());
}
}
}
}
这个方法主要是处理web配置的,主要是解析web.xml文件,不过我们项目中没用上,就不分析了,不过我们项目中用到了ServletContainerInitializer,这个类也是在这里加载的,我们主要来分析它。
2.1 查找ServletContainerInitializer
我们回忆下ServletContainerInitializer的实现,在我们的配置中,我们自定义的ServletContainerInitializer放在了META-INF/services/javax.servlet.ServletContainerInitializer文件中,这个文件的内容为:
org.apache.tomcat.demo.MyServletContainerInitializer
这个就是我们自定义的ServletContainerInitializer的包名.类名,我们进入processServletContainerInitializers()看看它是如何查找的:
public class ContextConfig implements LifecycleListener {
/** 当前类型是由哪个 ServletContainerInitializer 实例引入的 */
protected final Map<Class<?>, Set<ServletContainerInitializer>> typeInitializerMap =
new HashMap<>();
/** ServletContainerInitializer 实例引入的类型,类型可以有多个 */
protected final Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap =
new LinkedHashMap<>();
...
/**
* 处理 ServletContainerInitializer 的加载
*/
protected void processServletContainerInitializers() {
List<ServletContainerInitializer> detectedScis;
try {
WebappServiceLoader<ServletContainerInitializer> loader
= new WebappServiceLoader<>(context);
// 在这里完成加载的
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
...
ok = false;
return;
}
// 收集 annotation 里的 class
for (ServletContainerInitializer sci : detectedScis) {
initializerClassMap.put(sci, new HashSet<>());
HandlesTypes ht;
try {
// 得到 ServletContainerInitializer 上的 @HandlesTypes 注解
ht = sci.getClass().getAnnotation(HandlesTypes.class);
} catch (Exception e) {
...
continue;
}
...
// 得到 @HandlesTypes 指定的类型,可以指定多个
Class<?>[] types = ht.value();
...
for (Class<?> type : types) {
// @HandlesTypes 里的类型还可以是注解
if (type.isAnnotation()) {
handlesTypesAnnotations = true;
} else {
handlesTypesNonAnnotations = true;
}
// 保存
Set<ServletContainerInitializer> scis = typeInitializerMap.get(type);
if (scis == null) {
scis = new HashSet<>();
typeInitializerMap.put(type, scis);
}
scis.add(sci);
}
}
}
...
}
这个方法就是用来处理 ServletContainerInitializer 加载的,先是调用WebappServiceLoader#load(...)加载javax.servlet.ServletContainerInitializer并实例化,然后处理 ServletContainerInitializer 类上的@HandlesTypes注解。
1. 加载 ServletContainerInitializer
我们先来看看WebappServiceLoader#load(...)的方法:
public List<T> load(Class<T> serviceType) throws IOException {
// SERVICES值为“META-INF/services/”,传入的serviceType为“ServletContainerInitializer.class”
// 因此configFile值为“META-INF/services/javax.servlet.ServletContainerInitializer”
String configFile = SERVICES + serviceType.getName();
// 获取classLoader
ClassLoader loader = context.getParentClassLoader();
Enumeration<URL> containerResources;
// 使用classLoader查找 META-INF/services/javax.servlet.ServletContainerInitializer 文件
if (loader == null) {
containerResources = ClassLoader.getSystemResources(configFile);
} else {
containerResources = loader.getResources(configFile);
}
LinkedHashSet<String> containerServiceClassNames = new LinkedHashSet<>();
Set<URL> containerServiceConfigFiles = new HashSet<>();
while (containerResources.hasMoreElements()) {
URL containerServiceConfigFile = containerResources.nextElement();
containerServiceConfigFiles.add(containerServiceConfigFile);
// 处理文件,就是读取文件内容
parseConfigFile(containerServiceClassNames, containerServiceConfigFile);
}
// 省略“从jar包中读取”代码
...
// 实例化 serviceClass
return loadServices(serviceType, containerServiceClassNames);
}
这个方法所做的工作相当清晰,就是从classpath、jar包中查到META-INF/services/javax.servlet.ServletContainerInitializer 文件,然后读取内容,最后使用反射实例化,这块就不具体展开了。
2. @HandlesTypes 的功能
让我们回到ContextConfig#processServletContainerInitializers方法,加载完自定义实现的ServletContainerInitializer后,接下来的代码都是围绕@HandlesTypes在进行操作,这么一大段操作最终只是为了得到两个结构:
/** 当前类型是由哪个 ServletContainerInitializer 实例引入的 */
Map<Class<?>, Set<ServletContainerInitializer>> typeInitializerMap;
/** ServletContainerInitializer 实例引入的类型,类型可以有多个 */
Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap;
这块的操作就是一些基本的集合操作,就不过多分析了,关于这两个结构的内容我们后面会用到,在后面具体用到时,知道是在这里装入内容的就行了。
那这个@HandlesTypes究竟是个啥呢?它的代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
/**
* @return array of classes
*/
Class<?>[] value();
}
这个注解就一个属性,类型为Class数组,它的注释如下:
This annotation is used to declare an array of application classes which are passed to a {@link javax.servlet.ServletContainerInitializer}.
此批注用于声明应用程序类的数组,这些应用程序类将 传递给{@link javax.servlet.ServletContainerInitializer}。
这个注释看得并不是很明白,在我们的代码中没有用上@HandlesTypes,这里我们来看看spring的运用:
// HandlesTypes 指定的类型为 WebApplicationInitializer
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* webAppInitializerClasses:这里传入的就是 WebApplicationInitializer
*/
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses,
ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
// 这个for循环里处理了一些操作
for (Class<?> waiClass : webAppInitializerClasses) {
if (...) {
try {
// 反射实例化
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
...
}
}
}
...
// 遍历调用 WebApplicationInitializer 的方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
在spring中,实现ServletContainerInitializer的类为SpringServletContainerInitializer,功能如下:
SpringServletContainerInitializer在@HandlesTypes指定了类型为WebApplicationInitializer;- 这相处理后,
onStartup(...)方法的webAppInitializerClasses,传入Set<Class<?>>类型中的Class就为WebApplicationInitializer.class; - 使用反射实例化传入的
WebApplicationInitializer,调用其onStartup(...)方法; - 因此,在spring中,我们只要实现
WebApplicationInitializer,重写其onStartup(...)方法,也能实现servlet的加载,并不需要在META-INF/services文件夹中添加什么文件。
到了这里,应该就明白了@HandlesTypes的作用了吧,它就是用来过滤类的,我们可以使用它来让指定的Class传入到ServletContainerInitializer#onStartup(...)方法中。
2.2 处理类:processClasses(...)
让我们回到ContextConfig#webConfig(...)
protected void webConfig() {
...
if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
// 会在这里处理 @HandlesTypes 注解
processClasses(webXml, orderedFragments);
}
...
}
前面分析了 @HandlesTypes 注解的作用,这就让人不禁有个疑问:这些类是在哪里过滤的呢,要提前进行类加载(即得到类的Class对象,然后判断类型)吗?tomcat 处理 @HandlesTypes的操作在processClasses(...)方法中,我们且看下去。
跟着processClasses(...)一路执行,最终到了ContextConfig#processAnnotationsStream(...)方法:
protected void processAnnotationsStream(InputStream is, WebXml fragment,
boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache)
throws ClassFormatException, IOException {
// 传入的 is 就是class文件对应的输入流
ClassParser parser = new ClassParser(is);
JavaClass clazz = parser.parse();
// 检查类型,判断是否符合 `@HandlesTypes` 指定的类型
checkHandlesTypes(clazz, javaClassCache);
// 如果是 handlesTypesOnly,执行到这里就结束了
if (handlesTypesOnly) {
return;
}
// 意外之喜:发现了 @WebServlet、@WebFilter、@WebListener 的处理
processClass(fragment, clazz);
}
我们来细细分析这个方法的执行过程。
1. ClassParser
该方法的一开始,创建了ClassParser来解析class文件对应的输入流,我们来看看ClassParser:
/**
* Wrapper class that parses a given Java .class file. The method <A
* href ="#parse">parse</A> returns a <A href ="JavaClass.html">
* JavaClass</A> object on success. When an I/O error or an
* inconsistency occurs an appropriate exception is propagated back to
* the caller.
*
* 解析给定Java .class文件的包装器类。成功时,方法parse返回
* JavaClass对象。发生IO错误或不一致时,
* 会将适当的异常传播回调用方。
*
* The structure and the names comply, except for a few conveniences,
* exactly with the <A href="http://docs.oracle.com/javase/specs/">
* JVM specification 1.0</a>. See this paper for
* further details about the structure of a bytecode file.
*
* 除了一些方便之外,结构和名称
* 完全符合JVM规范1.0。有关字节码文件结构的更多详细信息,
* 请参见本文。
*/
public final class ClassParser {
...
}
从它的描述来看,就是另外实现了一个class解析器,如果我们直接使用java提供的Class对象,只需两步就能完成类型判断:Class.forname(...)进行加载、Class.isAssignableFrom(...)进行判断,tomcat为何要搞了个ClassParser,想了想,其主要目的,应该是避免提前进行类加载,使用Class对象,必须要先加载类,而这样操作,就把所有的类都加载了,有些类在整个jvm生命都可能用不到,因此造成了额外的内存浪费。
为避免类的提前加载,tomcat自己实现了jvm规范,搞了个ClassParser与JavaClass。
2. checkHandlesTypes(...)
这个方法就是用来检查当前类是否与@HandlesTypes指定的类相匹配的,它的注释如下:
For classes packaged with the web application, the class and each super class needs to be checked for a match with {@link HandlesTypes} or for an annotation that matches {@link HandlesTypes}.
对于与Web应用程序打包在一起的类,需要检查该类和每个 超类是否与 @HandlesTypes 指定的类相匹配, 或是否与 @HandlesTypes 指定的注解相匹配。
关于这个方法的执行过程,就不分析了。
3. handlesTypesOnly
ContextConfig#processAnnotationsStream(...)方法运行到这里,接着就是判断handlesTypesOnly的值了,如果为true就直接返回了,剧透下,这里的handlesTypesOnly的值就是为true,那么这个值有何玄机呢?
它的赋值在ContextConfig#scanWebXmlFragment(...)方法,代码如下:
// Only need to scan for @HandlesTypes matches if any of the
// following are true:
// - it has already been determined only @HandlesTypes is required
// (e.g. main web.xml has metadata-complete="true"
// - this fragment is for a container JAR (Servlet 3.1 section 8.1)
// - this fragment has metadata-complete="true"
boolean htOnly = handlesTypesOnly || !fragment.getWebappJar() ||
fragment.isMetadataComplete();
上面的翻译是说,这个属性是表示仅仅只处理@HandlesTypes,在以下几点情况下为true:
- 仅仅只需要处理
@HandlesTypes,意思是可以在外面设置handlesTypesOnly的值; - 以 jar 包运行方式,应该是指内嵌的方式运行,区别于以往运行时都要搞个
webapp文件夹、要web.xml文件; - 在web.xml文件中指定
metadata-complete="true".
本系列的示例demo中没有webapp文件夹,也没有web.xml文件,handlesTypesOnly的值也是true.
4. processClass(...)
虽然ContextConfig#processAnnotationsStream(...)方法运行到if (handlesTypesOnly) 就返回了,但我们还是来看看processClass(...)干了些什么:
protected void processClass(WebXml fragment, JavaClass clazz) {
AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
if (annotationsEntries != null) {
String className = clazz.getClassName();
for (AnnotationEntry ae : annotationsEntries) {
String type = ae.getAnnotationType();
if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) {
processAnnotationWebServlet(className, ae, fragment);
}else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) {
processAnnotationWebFilter(className, ae, fragment);
}else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) {
fragment.addListener(className);
} else {
// Unknown annotation - ignore
}
}
}
}
如果对servlet 3.0 比较熟悉,就会发现这是3个熟悉的面孔:
@WebServlet:用来标记servlet类,标记了该注解的类会被注册为Servlet@WebFilter:用来标记filter类,标记了该注解的类会被注册为Filter@WebListener:用来标记listener类,标记了该注解的类会被注册为Listener
在 servlet 3.0 时代,我们注册servlet、filter、listener,只需要使用对应的注解就可以了,十分方便!不过,这个“方便”是有前提的,前面刚 分析过handlesTypesOnly为true时,processClass(...)方法是不会执行的!不过springboot框架为我们解决了这个问题,在springboot中,我们依然可以大胆地使用以上3个注解,关于它的解决办法,可以参考springboot web应用之servlet组件的注册流程。
最后,我们再来看看它最终把servlet、filter、listener注解到了哪里:
servlet:WebXml#servlets,类型为Map<String,ServletDef>filter:WebXml#filters,类型为Map<String,FilterDef>listener:WebXml#listeners,类型为Set<String>
这些位置还是比较重要的,后面我们处理servlet、filter、listener时,就是从这里拿到的。
2.3 配置Context:ContextConfig#configureContext
让我们再回到ContextConfig#webConfig(...)方法:
protected void webConfig() {
...
// 应用配置,将 webXml 中的配置信息应用用Context
configureContext(webXml);
...
}
虽然我们的项目中并没有web.xml文件,但tomcat依然会生成webXml对象,该对象用来保存项目中的配置,如上面得到的servlet、filter、listener,在这之后会调用ContextConfig#configureContext方法将这些配置应用到context,这里我们主要关注servlet三大组件的应用,进入ContextConfig#configureContext方法:
private void configureContext(WebXml webxml) {
...
for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
for (String listener : webxml.getListeners()) {
context.addApplicationListener(listener);
}
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
// 省略了好多属性配置
...
wrapper.setName(servlet.getServletName());
wrapper.setServletClass(servlet.getServletClass());
// 添加到 Context 子 容器
context.addChild(wrapper);
}
...
}
这个方法非常长,我这里仅列出了servlet、filter、listener三大组件的处理,最终这些组件都会添加到Context中,后面我们会看到这些组件是如果拿出来使用。
2.4 保存ServletContainerInitializer
让我们再回到ContextConfig#webConfig(...)方法:
protected void webConfig() {
...
// 将 ServletContainerInitializer 添加到Context
if (ok) {
for (Map.Entry<ServletContainerInitializer,
Set<Class<?>>> entry : initializerClassMap.entrySet()) {
if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(entry.getKey(), null);
} else {
context.addServletContainerInitializer(entry.getKey(), entry.getValue());
}
}
}
...
}
获得了ServletContainerInitializer,接下来就是找个地方把它们保存起来了,我们来看看它保存到哪里了,进入StandardContext#addServletContainerInitializer方法:
private Map<ServletContainerInitializer,Set<Class<?>>> initializers =
new LinkedHashMap<>();
/**
* 添加到 StandardContext#initializers 结构中
*/
public void addServletContainerInitializer(
ServletContainerInitializer sci, Set<Class<?>> classes) {
initializers.put(sci, classes);
}
可以看到,最终是添加到了StandardContext#initializers中了。
到此,ServletContainerInitializer的加载就算完成了。
3. 执行ServletContainerInitializers#onStartup(...)方法
我们再回到StandardContext#startInternal()方法,继续进行下面的分析:
protected synchronized void startInternal() throws LifecycleException {
// 省略了一些代码
...
// 3. 处理 ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
// 执行 ServletContainerInitializers#onStartup(...)方法
entry.getKey().onStartup(entry.getValue(), getServletContext());
} catch (ServletException e) {
...
}
}
...
}
这块代码所做的工作就是执行ServletContainerInitializers#onStartup(...)方法,上一 节中,费了老大劲终于集齐了所有的ServletContainerInitializer,保存的结构为StandardContext#initializers:
/**
* 保存 ServletContainerInitializer 的实例类,
* key:ServletContainerInitializer 实例,也就是
* `META-INF/services/xxx.ServletContainerInitializer`指定类的实例
* value:引入类,就是在 ServletContainerInitializer 上使用 @HandlesTypes 指定的类
*
*/
private Map<ServletContainerInitializer, Set<Class<?>>> initializers =
new LinkedHashMap<>();
我们来看下ServletContainerInitializers#onStartup(...)的参数就明白了:
/**
* @param c:Class 集合,由 @HandlesTypes 指定的类
* @param ctx:ServletContext,由 servlet 提供
*/
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
前面分析了Set<Class<?>> c参数的获取,接着我们来看看ServletContext ctx的获取,也就是以下代码中,getServletContext()所做的工作:
// 执行 ServletContainerInitializers#onStartup(...)方法
// getServletContext():获取 ServletContext
entry.getKey().onStartup(entry.getValue(), getServletContext());
3.1 StandardContext#getServletContext()
StandardContext#getServletContext()方法如下:
protected ApplicationContext context = null;
/**
* 获取 ServletContext
*/
@Override
public ServletContext getServletContext() {
// 这个 context 指的是 ApplicationContext
if (context == null) {
context = new ApplicationContext(this);
if (altDDName != null)
context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
}
return context.getFacade();
}
最终返回的ServletContext是调用context.getFacade()返回的,我们来看看ApplicationContext#getFacade()的实现:
/**
* ApplicationContext 实现了 ServletContext
*/
public class ApplicationContext implements ServletContext {
/**
* 提供对外访问的类 ServletContext
*/
private final ServletContext facade = new ApplicationContextFacade(this);
/**
* 获取 ServletContext
*/
protected ServletContext getFacade() {
return this.facade;
}
...
}
ApplicationContext中有一个成员变量facade,类型为ApplicationContextFacade,ApplicationContext#getFacade()就是用来返回这个facade的,继续查看ApplicationContextFacade的构造方法:
public class ApplicationContextFacade implements ServletContext {
/**
* 这个 context 就是前面的 ApplicationContext
*/
private final ApplicationContext context;
/**
* 构造方法
*/
public ApplicationContextFacade(ApplicationContext context) {
...
this.context = context;
...
}
...
}
这是个典型的门面模式,在ApplicationContextFacade方法中,可以看到它实例调用的是ApplicationContext提供的方法:
public class ApplicationContextFacade implements ServletContext {
...
@Override
public int getMajorVersion() {
return context.getMajorVersion();
}
@Override
public int getMinorVersion() {
return context.getMinorVersion();
}
...
}
其他方法也是这么调用的,就不一一列举了。
3.2 注册servlet:ServletContext#addServlet(...)
我们回到MyServletContainerInitializer类,执行ServletContainerInitializers#onStartup(...)方法方法后,我们自定义的MyServletContainerInitializer#onStartup会被调用到:
public void onStartup(Set<Class<?>> clsSet, ServletContext servletContext)
throws ServletException {
MyHttpServlet servlet = new MyHttpServlet();
ServletRegistration.Dynamic registration = servletContext.addServlet("servlet", servlet);
// loadOnStartup 设置成 -1 时,只有在第一次请求时,才会调用 init 方法
registration.setLoadOnStartup(-1);
registration.addMapping("/*");
}
可以看到,注册servlet是通过ServletContext#addServlet(...)方法进行的,我们跟进去,进入的是ApplicationContextFacade#addServlet(...):
@Override
public ServletRegistration.Dynamic addServlet(String servletName,
Servlet servlet) {
if (SecurityUtil.isPackageProtectionEnabled()) {
return (ServletRegistration.Dynamic) doPrivileged("addServlet",
new Class[]{String.class, Servlet.class},
new Object[]{servletName, servlet});
} else {
// 这里会被执行
return context.addServlet(servletName, servlet);
}
}
这个context就是ApplicationContext,我们继续,进入ApplicationContext#addServlet(...)方法:
private ServletRegistration.Dynamic addServlet(String servletName, String servletClass,
Servlet servlet, Map<String,String> initParams) throws IllegalStateException {
...
// 获取 wrapper
Wrapper wrapper = (Wrapper) context.findChild(servletName);
// 如果servletName对应的wrapper不存在,就会将servlet包装为wrapper,并添加到Context的子容器中,
// 这里的wrapper具体实现为StandardWrapper,Context的子容器为Wrapper
if (wrapper == null) {
wrapper = context.createWrapper();
wrapper.setName(servletName);
// 这个 context 为 StandardContext
context.addChild(wrapper);
} else {
...
}
// 设置 servlet 的一些参数,设置到 wrapper
...
if (initParams != null) {
for (Map.Entry<String, String> initParam: initParams.entrySet()) {
wrapper.addInitParameter(initParam.getKey(), initParam.getValue());
}
}
// 返回注册对象
ServletRegistration.Dynamic registration =
new ApplicationServletRegistration(wrapper, context);
...
return registration;
}
添加servlet的操作还是比较简单的,流程如下:根据servletName查找wapper,如果wapper不存在,就将的servlet包装为Wrapper对象,添加到Context的子容器中,最后返回该servlet的注册对象registration。
这个registration是个啥呢,为何对它操作可以影响到servlet?我们进入ApplicationServletRegistration:
public class ApplicationServletRegistration implements ServletRegistration.Dynamic {
/** servlet 的包装类,实现为 StandardWrapper */
private final Wrapper wrapper;
/** 实现类为 StandardContext */
private final Context context;
/**
* 赋值操作
*/
public ApplicationServletRegistration(Wrapper wrapper, Context context) {
this.wrapper = wrapper;
this.context = context;
}
/**
* 设置 loadOnStartup 属性
*/
@Override
public void setLoadOnStartup(int loadOnStartup) {
wrapper.setLoadOnStartup(loadOnStartup);
}
...
}
ApplicationServletRegistration实现了ServletRegistration.Dynamic,ServletRegistration.Dynamic是servlet提供的,用来处理servlet注册的,ApplicationServletRegistration中有两个成员变量:wrapper、context,其中wrapper为servlet 的包装类,实现为 StandardWrapper。
我们在调用ServletRegistration.Dynamic#setLoadOnStartup(...)方法时,最终会调用Wrapper#setLoadOnStartup(...)方法,其他属性设置的方式也是类似的操作,最终都会调用Wrapper相关的方法,这也就是registration能处理servlet属性的原因了。
4. 加载servlet组件
让我们回到StandardContext#startInternal方法:
protected synchronized void startInternal() throws LifecycleException {
// 省略了一些代码
...
// 4. 加载 Listener
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// 5. 加载 filter
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// 6. 处理 servlet,加载与初始化,Context 的 child 为 Wrapper,用来存放servlet的
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
...
}
前面做了那么多的准备工作,终于开始加载servlet的三大组件了!
4.1 加载Listener
Listener的加载操作如下:
// 4. 加载 Listener
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
我们进入StandardContext#listenerStart方法:
public boolean listenerStart() {
// findApplicationListeners:获取 listeners,
// 里面的内容就是前面在 ContextConfig#configureContext 添加的
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
boolean ok = true;
for (int i = 0; i < results.length; i++) {
try {
String listener = listeners[i];
// 创建监听器实例
results[i] = getInstanceManager().newInstance(listener);
} catch (Throwable t) {
...
}
}
// 省略了一些代码
...
// 处理各种 listener,真想不到 servlet 竟然提供了这么多类型的 Listener
List<Object> eventListeners = new ArrayList<>();
List<Object> lifecycleListeners = new ArrayList<>();
for (Object result : results) {
if ((result instanceof ServletContextAttributeListener)
|| (result instanceof ServletRequestAttributeListener)
|| (result instanceof ServletRequestListener)
|| (result instanceof HttpSessionIdListener)
|| (result instanceof HttpSessionAttributeListener)) {
eventListeners.add(result);
}
if ((result instanceof ServletContextListener)
|| (result instanceof HttpSessionListener)) {
lifecycleListeners.add(result);
}
}
// 省略了一些 代码
...
}
这个方法还是挺清晰的,大致流程如下:
- 获取所有的
listener,在ContextConfig#configureContext方法中,会将listener添加到Context中,现在findApplicationListeners(...)方法来获取了; - 获取到
listener后,使用反射进行实例化; - 接下来就是对
Listener进行分类了,想不到servlet提供的listener类型竟然有这么多。
得到的这些listener在哪里运行呢?由于listener类型的不同,监听的事件也不同,因此他们执行的时机也各不相同,只能根据具体的listener类型来分析了。
这里我们还是以spring为例,看看spring提供的一个重要listner:ContextLoaderListener,它实现了ServletContextListener:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
...
}
...
}
这个ServletContextListener#contextInitialized方法是在哪里执行的呢?还是在StandardContext#listenerStart方法:
public boolean listenerStart() {
// 省略了好多代码
...
// 处理 ServletContextListener
for (Object instance : instances) {
if (!(instance instanceof ServletContextListener)) {
continue;
}
// 调用 ServletContextListener#contextInitialized(...)
ServletContextListener listener = (ServletContextListener) instance;
try {
fireContainerEvent("beforeContextInitialized", listener);
// 在这里调用的
if (noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable t) {
...
}
}
return ok;
}
在StandardContext#listenerStart方法的最后,会遍历所有的listener,找到ServletContextListener类型的,然后一一执行ServletContextListener#contextInitialized方法。
4.2 加载filter
处理 filter 加载的代码如下:
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
我们进入StandardContext#filterStart方法:
/** 保存 ApplicationFilterConfig */
private Map<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();
/**
* 处理 filter
*/
public boolean filterStart() {
boolean ok = true;
synchronized (filterConfigs) {
filterConfigs.clear();
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
try {
// 生成 filterConfig 对象,然后保存 filter
ApplicationFilterConfig filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch (Throwable t) {
...
}
}
}
return ok;
}
这个方法只是将filter转换为ApplicationFilterConfig,然后保存到StandardContext#filterConfigs中,看来在这一步还没用上filter。
4.3 加载servlet
处理servlet加载的代码如下:
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
loadOnStartup(findChildren())包含两个方法:
findChildren():查找当前子容器,通过前面的分析可知,Context存放的是Wrapper,这个Wrapper又是servlet的包装类;loadOnStartup:处理servlet的加载的方法;
我们进入StandardContext#loadOnStartup方法:
public boolean loadOnStartup(Container children[]) {
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
// 根据 loadOnStartup 排序
for (Container child : children) {
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
// 如果 loadOnStartUp < 1,则不会在启动时加载
if (loadOnStartup < 0) {
continue;
}
// 处理排序操作,TreeMap 本身就是有序的,相同的 loadOnStartup 值放入同一个 ArrayList 中
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
// 加载
wrapper.load();
} catch (ServletException e) {
...
}
}
}
return true;
}
这个方法做了两件事:
- 排序:根据
loadOnStartup的值排序,值小于0,则不进行处理,值相同则放入同一个ArrayList中 - 加载:遍历排好序的
wrapper,逐一调用其Wrapper#load()方法
我们来看看StandardWrapper#load做了什么:
/**
* wrapper 的加载操作
*/
public synchronized void load() throws ServletException {
// 加载 servlet,就是进行实例化的操作
instance = loadServlet();
if (!instanceInitialized) {
// 初始化servlet,就是执行 Servlet#init(...) 方法
initServlet(instance);
}
// 其他省略
...
}
这个方法先是调用loadServlet()方法得到servlet实例,然后再调用initServlet(...)方法进行初始化,我们先看loadServlet()方法,核心代码如下:
/**
* 加载 serlvet,得到的是 servlet实例
*/
public synchronized Servlet loadServlet() throws ServletException {
// 当前 servlet 已实例化了,直接返回
if (!singleThreadModel && (instance != null))
return instance;
Servlet servlet;
...
try {
// 实例化 servlet
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
...
}
...
return servlet;
}
这个方法就只是处理servlet对象的获取,当对象不存在时,就调用反射实例化了。
继续看看initServlet(...)方法,核心代码如下:
private synchronized void initServlet(Servlet servlet) throws ServletException {
// 判断是否已初始化
if (instanceInitialized && !singleThreadModel) return;
try {
// 调用 Servlet#init 方法
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
SecurityUtil.doAsPrivilege("init", servlet, classType, args);
success = true;
} finally {
if (!success) {
SecurityUtil.remove(servlet);
}
}
} else {
servlet.init(facade);
}
instanceInitialized = true;
} catch (UnavailableException f) {
...
}
}
这个方法就是用来执行servlet#init方法的。
5. 总结
本文分析了Context的启动流程,总结如下:
- 处理
servlet 3.0规范,也就是加载ServletContainerInitializer类 - 解析web配置,不仅是指
web.xml,还有@HandlersTypes指定的类,以及@WebServlet、@WebFilter、@WebListener注解 - 将解析到的配置应用于
Context,在这一步会把解析到的servlet、filter、listener添加到Context中 - 执行
ServletContainerInitializer#onStartUp方法,将配置内容加入到Context后,接着就是应用这些配置了 - 加载
servlet三大组件:servlet、filter、listener
限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。
本文首发于微信公众号 Java技术探秘,如果您喜欢本文,欢迎关注该公众号,让我们一起在技术的世界里探秘吧!