正文
为什么关心 Tomcat 中一个 web 应用的加载过程?在前面的文章中看过多次 Tomcat 的组件结构图,这里再贴出来回顾一下:
之前的
Tomcat 7 启动分析
系列文章中看到 Tomcat 启动的时候将会解析 server.xml,根据里面所配置的各个节点信息逐一初始化和启动相应组件(即分别调用它们的 init 和 start 方法),但浏览一下 Tomcat 7 源码中的 server.xml 的内容,里面对应上图中的已经默认配置的各级组件包括 Server、Service、Engine、Connector、Host、Valve 。上图中的 Context 组件实际就是我们通常所说的一个 web 应用,有趣的是在 server.xml 中并没有配置该组件,而我们默认启动的时候实际上已经有好几个 web 应用可以访问了:
这个到底是怎么回事?
看过前面的
Tomcat 7 的一次请求分析
系列文章的人应该知道,浏览器一次请求送到 Tomcat 服务器之后,最终会根据浏览器中的 url 路径找到相应的实际要访问的 web 应用的 context 对象(默认即
org.apache.catalina.core.StandardContext
类的实例)。比如访问的 url 为
http://localhost:8080/
,那么将会送到上图 ROOT 文件夹表示的 web 应用中,访问的 url 为
http://localhost:8080/docs
,那么将访问 docs 文件夹表示的 web 应用。所以能够猜到的是在 Tomcat 启动完成后,必定容器内部已经构造好了表示相应web应用的各个 context 对象。
本文就对这个问题一探究竟。在
Tomcat 7 服务器关闭原理
的开头提到,默认的配置下 Tomcat 启动完之后会看到后台实际上总共有 6 个线程在运行:
前面的几篇文章中涉及了
main
、
http-bio-8080-Acceptor-0
、
http-bio-8080-AsyncTimeout
、
ajp-bio-8009-Acceptor-0
、
ajp-bio-8009-AsyncTimeout
,已经谈到了这些线程的作用,它们是如何产生并响应请求的。但有一个线程没有说,即
ContainerBackgroundProcessor[StandardEngine[Catalina]]
,而本文要解答的问题奥秘就在这个线程之中。
先看看这个线程是如何产生的,其实从命名就可以看出一些端倪,它叫做容器后台处理器,并且跟 StandardEngine 关联起来,它的产生于作用也同样如此。
Tomcat 7 中所有的默认容器组件( StandardEngine、StandardHost、StandardContext、StandardWrapper )都会继承父类
org.apache.catalina.core.ContainerBase
,在这些容器组件启动时将会调用自己内部的 startInternal 方法,在该方法内部一般会调用父类的 startInternal 方法( StandardContext 类的实现除外),比如
org.apache.catalina.core.StandardEngine
类中的 startInternal 方法:
最后的
super.startInternal()
即调用父类
org.apache.catalina.core.ContainerBase的startInternal
方法,在该方法最后:
第 6 行设置了
LifecycleState.STARTING
状态(这样将向容器发布一个
Lifecycle.START_EVENT
事件),这一行的作用本文后面会提到,暂且按下不表。第 9 行调用 threadStart 方法,看看 threadStart 方法的代码:
这里可以看到如果两个前置校验条件通过的话将会启动一个线程,并且线程的名字即以
ContainerBackgroundProcessor[
开头,线程名字后面取的是对象的 toString 方法,以 StandardEngine 为例,看看
org.apache.catalina.core.StandardEngine
的 toString 方法实现:
以上解释了这个后台线程的来历。
但这里有一个问题,既然 StandardEngine、StandardHost 都会调用
super.startInternal()
方法,按默认配置,后台理应产生两个后台线程,实际为什么只有一个?
回到
org.apache.catalina.core.ContainerBase
的 threadStart 方法,在启动线程代码之前有两个校验条件:
容器组件对象初始化时 thread 为
null
,backgroundProcessorDelay 是
-1
但
org.apache.catalina.core.StandardEngine
在其自身构造函数中做了一点修改:
构造函数最后将父类的 backgroundProcessorDelay 的值由
-1
改成了
10
,所以 Tomcat 启动解析 xml 时碰到一个 Engine 节点就会对应产生一个后台处理线程。