正文
前一篇文章
讲了
org.apache.catalina.startup.HostConfig
的 lifecycleEvent 方法中所做的事情。最后看到在 Tomcat 启动时或启动后(后台线程定时扫描)会调用 HostConfig 类的 deployApps 方法:
可以看到这里部署应用有三种方式:XML 文件描述符、WAR 包、文件目录。三种方式部署的总体流程很相似,都是一个 web 应用分配一个线程来处理,这里统一放到与 Host 内部的线程池对象中( startStopExecutor ),所以有时会看到在默认配置下 Tomcat 启动后可能有一个叫
-startStop-
的线程还会运行一段时间才结束。但浏览这三种部署方式的实现代码,里面都是构建一个 Context 对象,并将构建好的 Context 对象与 Host 组件关联起来(即调用
host.addChild(context)
这句,具体代码在 HostConfig 类的
deployDescriptor(ContextName cn, File contextXml)
、
deployDirectory(ContextName cn, File dir)
、
deployWAR(ContextName cn, File war)
三个方法中,这里不再贴出代码来详细分析)。
前一篇文章只分析到这步,可以看出与一个 web 应用相对应的一个 Context 对象已经构建出来了,但如果容器只执行到这里根本无法响应一个浏览器的一次请求。就 web 服务器的实现来看一次请求过来除了需要根据内部 Context 构建找到这次请求访问的web应用具体所对应的 Context 对象,还需要包含 web 应用中具体的哪个 Servlet 来处理这次请求,中间是否还需要执行相应的过滤器( filter )、监听器( listener )等,做过 java 的 web 开发的同学都知道,这些信息是配置在一个 web 应用的
WEB-INF\web.xml
文件的(servlet3 中已经支持将这些配置信息放到 Java 文件的注解中,但万变不离其宗,总归要在 web 应用的某个地方说明,并在容器启动时加载,这样才能真正提供 web 服务,响应请求)。
看到这里可以猜到 Tomcat 容器加载 web 应用时必定会有对于每个应用的 web.xml 文件的解析过程,本文就来看看这个解析过程。
在本文开头提到的三种部署应用的实现代码中有一些共通的代码,这里摘出来说明一下:
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
context.addLifecycleListener(listener);
host.addChild(context);
第一段是在所有 Context 对象构建时会添加一个监听器,这里监听器的类名是 StandardHost 类的实例变量 configClass ,其默认值就是
org.apache.catalina.startup.ContextConfig
。第二段是将当前构建的 Context 对象添加到父容器 Host 对象中。
先看 StandardHost 的 addChild 方法的实现:
可以看到这段代码最后调用了父类的 addChild 方法:
这里看下 addChildInternal 方法的实现:
可以看到会调用子容器的 start 方法,就是指调用 StandardContext 的 start 方法。
即给 host 对象添加子容器时将会调用子容器的 start 方法,按照
前面文章
的分析,调用 StandardContext 的 start 方法最终会调用
org.apache.catalina.core.StandardContext
类的 startInternal 方法(该方法代码较长,建议自己阅读,不再贴出),这里将会发布一系列事件,按调用前后顺序这些事件包括:
BEFORE_INIT_EVENT
、
AFTER_INIT_EVENT
、
BEFORE_START_EVENT
、
CONFIGURE_START_EVENT
、
START_EVENT
、
AFTER_START_EVENT
。