为何关心 Tomcat 中一个 web 应用的加载过程?在前面的文章中看过屡次 Tomcat 的组件结构图,这里再贴出来回顾一下: 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 对象。apache
本文就对这个问题一探究竟。在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 关联起来,它的产生于做用也一样如此。bash
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
方法,在该方法最后:
LifecycleState.STARTING
状态(这样将向容器发布一个
Lifecycle.START_EVENT
事件),这一行的做用本文后面会提到,暂且按下不表。第 9 行调用 threadStart 方法,看看 threadStart 方法的代码:
ContainerBackgroundProcessor[
开头,线程名字后面取的是对象的 toString 方法,以 StandardEngine 为例,看看
org.apache.catalina.core.StandardEngine
的 toString 方法实现:
但这里有一个问题,既然 StandardEngine、StandardHost 都会调用super.startInternal()
方法,按默认配置,后台理应产生两个后台线程,实际为何只有一个?app
回到org.apache.catalina.core.ContainerBase
的 threadStart 方法,在启动线程代码以前有两个校验条件: 函数
null
,backgroundProcessorDelay 是
-1
org.apache.catalina.core.StandardEngine
在其自身构造函数中作了一点修改:
-1
改为了
10
,因此 Tomcat 启动解析 xml 时碰到一个 Engine 节点就会对应产生一个后台处理线程。
讲完了这个后台处理线程的产生,看看这个线程所做的事情,再看下这个线程的启动代码: post
而这个 backgroundProcess 方法在 ContainerBase 内部已经给出了实现: url
Lifecycle.PERIODIC_EVENT
事件。
上面就是 Tomcat 7 的后台处理线程所做的事情的概述,在 Tomcat 的早期版本中有一些后台处理的事情原来是在各个组件内部分别自定义一个线程并启动,在 Tomcat 5 中改为了全部后台处理共享同一线程的方式。spa
回到本文要解答的问题,web 应用如何加载到容器中的?在 ContainerBase 类的 backgroundProcess 方法的最后:
fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
复制代码
向容器注册了一个PERIODIC_EVENT
事件。前面说道默认的ContainerBackgroundProcessor[StandardEngine[Catalina]]
线程会按期(默认为 10 秒)执行 Engine、Host、Context、Wrapper 各容器组件及与它们相关的其它组件的 backgroundProcess 方法,因此也会按期向 Host 组件发布一个PERIODIC_EVENT
事件,这里看下 StandardHost 都会关联的一个监听器org.apache.catalina.startup.HostConfig
:
在 Tomcat 启动解析 xml 时
org.apache.catalina.startup.Catalina
类的 386 行:digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"))
在 HostRuleSet 类的 addRuleInstances 方法中:
![]()
第 9 到 12 行看到,全部 Host 节点都会添加一个
org.apache.catalina.startup.HostConfig
对象做为org.apache.catalina.core.StandardHost
对象的监听器
在 HostConfig 的 lifecycleEvent 方法中能够看到若是 Host 组件收到了 Lifecycle.PERIODIC_EVENT 事件的发布所做出的响应(若是对 Tomcat 7 的 Lifecycle 机制不清楚能够看下Tomcat 7 启动分析(五)Lifecycle 机制和实现原理:
PERIODIC_EVENT
将会执行 check 方法。第 19 行,若是发布的事件是
START_EVENT
则执行 start 方法。check 方法和 start 方法最后都会调用 deployApps() 方法,看下这方法的实现:
本文前面提到默认状况下组件启动的时候会发布一个Lifecycle.START_EVENT
事件(在org.apache.catalina.core.ContainerBase
类的 startInternal 方法倒数第二行),回到 HostConfig 的 lifecycleEvent 方法中,因此默认启动时将会执行 HostConfig 的 start 方法,在该方法的最后:
if (host.getDeployOnStartup())
deployApps();
复制代码
由于默认配置 host.getDeployOnStartup() 返回 true ,这样容器就会在启动的时候直接加载相应的 web 应用。
固然,若是在 server.xml 中 Host 节点的 deployOnStartup 属性设置为 false ,则容器启动时不会加载应用,启动完以后不能当即提供 web 应用的服务。但由于有上面提到的后台处理线程在运行,会按期执行 HostConfig 的 check 方法: