从前几篇文章可以知道,Tomcat包含很多组件,并且一个组件可以包含多个组件,当Tomcat启动时,只需要启动最上层的组件,那么包含于该组件中的其他组件也会一并启动,关闭也一样。这就是通过实现org.apache.catalina.Lifecycle
接口来实现的单一启动/关闭的效果。
Tomcat的生命周期主要涉及到4种类型:Lifecycle
、LifecycleEvent
、LifecycleState
和LifecycleListener
,Tomcat也提供了LifecycleBase
抽象类来简化生命周期的处理,它实现了Lifecycle
接口,并提供了钩子函数来扩展各个组件在生命周期中的处理行为。
生命周期的基本类图如下:

Lifecycle接口
Tomcat中的组件可以包含多个其他组件,这些组件的启动和关闭并不需要进行单独的启动和关闭,而是只启动或关闭最上层的组件即可使全部组件都能够启动或关闭,这种单一启动/关闭机制就是通过Lifecycle
接口来实现的。
|
|
接口中定义了很多触发事件,常用的例如before_init
、after_init
、before_start
、start
、after_start
等等,只看名字也能知道是做什么的了。
start
和stop
方法是对组件进行启动和关闭的操作;addLifecycleListener
、removeLifecycleListener
和findLifecycleListeners
都是与事件监听相关的。一个组件可以注册多个事件监听器来监听该组件对应的某些事件,当触发了该事件时,会通知相应的监听器。
这里面还有一个很有意思的内部接口SingleUse
,它是一个标记接口,用于指示实例应该只使用一次。当一个组件实例实现了这个接口会在stop
方法完成时自动调用destroy
方法。例如在LifecycleBase
中的stop
方法:
|
|
作为一个标记接口,并没有需要实现的方法,仅仅代表了一种能力,例如实现了java.io.Serializable
接口的实例,表示该实例可以序列化,在判断时会使用instanceof
关键字来进行判断。
其实在java.util.Map<K,V>
接口中也定义了一个内部接口Map.Entry<K, V>
:
|
|
那么为什么要定义一个内部接口呢,内部接口的作用是什么?具体现在我也不太清楚,这里先标记一下,有空专门研究一下。
LifecycleEvent类
LifecycleEvent
类的实例表示一个生命周期的事件,类的定义如下:
|
|
在构造方法中,第一个参数为生命周期的组件,第二个参数为事件类型,第三个参数为传入的数据,可以看成是对这三种类型的封装。
另外介绍一下EventObject
对象,它里面定义了一个事件源对象,所谓事件源就是事件发生的地方,而在Tomcat的设计中,事件源就是实现了Lifecycle
接口的各个需要管理生命周期的组件。每个组件都继承自LifecycleBase
,那么组件就是通过LifecycleBase
来实现事件源的传递,这样在LifecycleBase
触发事件的时候,可以通过事件源(也就是相当于当前组件this)构建EventObject
.这样以来LifecycleListener
就可以通过事件对象获取到事件源,从而做一些与事件源相关的操作。如果还是不太清楚的话,继续往下看。
LifecycleListener接口
一个生命周期的事件监听器是该接口的实例,该接口的定义如下:
|
|
只有一个方法lifecycleEvent
,看到该方法的参数正是上面所讲到的LifecycleEvent
,当某个事件监听器监听到相关事件时,会调用该方法。例如,当Server
组件启动时,看下StandardServer
中的startInternal
方法的代码:
|
|
fireLifecycleEvent
方法继承自LifecycleBase
:
|
|
可以看到,在该方法中,创建了一个LifecycleEvent
对象,并将当前对象(StandardServer
)作为第一个参数传入LifecycleEvent
构造器中,然后监听器会调用lifecycleEvent
方法来执行具体的操作。
LifecycleBase类
LifecycleBase
实现了Lifecycle
接口,添加了几个新的方法如setStateInternal
(更新组件状态)、fireLifecycleEvent
(触发LifecycleEvent),以及一些钩子方法例如initInternal
、startInternal
等。

例如上文中提到的fireLifecycleEvent
方法,用来触发事件;再例如,执行init
方法时,会调用抽象方法initInternal
:
|
|
具体的规则在子类中实现。
该类中定义了两个重要的变量:lifecycleListeners
和state
:
|
|
lifecycleListeners
用来保存监听器,state
表示当前生命周期的状态。
lifecycleListeners属性
首先来看下lifecycleListeners
,它是CopyOnWriteArrayList
的类型,这里简单介绍一下:
Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
那么在LifecycleBase
中,为什么将lifecycleListeners
变量定义为这种类型?从上面的分析可知,CopyOnWriteArrayList
适用于读多写少的情况。回顾一下Tomcat源码:Catalina启动流程中提到的createStartDigester
方法,在该方法中有如下代码:
|
|
对于服务器组件Server
,监听器是在调用StandardServer
的构造方法和解析配置文件server.xml
时通过调用addLifecycleListener
方法来添加到lifecycleListeners
变量中的,那么当Tomcat启动后,就基本不会再添加新的Listener了。但注意,是基本不会添加,并不绝对,例如org.apache.catalina.startup.HostConfig
,它也是一个监听器,看下其中的reload
方法:
|
|
该方法是重新加载一个context,那么可以看到,这里为context
增加了一个监听器,该监听器用来确保在重新加载context时,将之前WAR包解压出来的目录删除,并且将dcoBase设置到指定的WAR。
在使用CopyOnWriteArrayList
时需要注意两个问题:
- 内存占用问题
- 数据一致性问题
内存占用问题:因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,如果列表中的对象比较大,假设为100M,那么再写入100M的对象进去,内存就会占用200M,那么这个时候很有可能造成频繁的Yong GC和Full GC,从而会导致整个系统响应时间过长。
数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据能够立即读到,那么请不要使用CopyOnWrite容器。
总体来说,一般组件启动之后,就基本不会再添加新的监听器了,所以使用CopyOnWriteArrayList
类型是很合适的。
state属性
再来看下state
属性,该属性被volatile
关键字修饰,保证了state
属性的可见性,但也仅仅是可见性,而不具有原子性,所以,它与synchronized
关键字相比,少了原子性,可以看做是”轻量级的synchronized
“。
这里为什么用volatile
关键字修饰?
先考虑一下是否需要原子性。我们现在可以知道,一个组件的状态是在调用生命周期的init
、start
等Lifecycle
接口中定义的生命周期行为的方法时才会被设置,一个组件不可能在同一时间调用不同的生命周期行为,所以原子性是不必要的。
再来考虑一下可见性。当组件的状态改变时,当然是需要立即被读取到,通过组件生命周期的状态来判断是否应该执行指定的生命周期的行为,例如一个组件已经执行了destroy
方法,那么就不可能再调用该组件的start
方法,所以可见性是必须的。
LifecycleState类
LifecycleState
是一个枚举类型,定义如下:
|
|
LifecycleState
包含两个属性:available
和lifecycleEvent
。
available:判断在当前状态下是否可以调用除getter/setter方法以外的public方法以及生命周期中的方法。当前状态为以下状态时返回
true
:- STARTING
- STARTED
- STOPPING_PREP
getLifecycleEvent:获取处于此状态的组件正在进行的事件
例如,在StandardServer
中的addService
方法中:
|
|
如果当前Server正在启动或者已经启动,就可以直接启动服务组件service
。
总结
以上就是Tomcat生命周期的核心内容,了解了生命周期,就可以很清楚的明白Tomcat的启动流程,通过Lifecycle,Tomcat只需启动最顶层组件Server
,即可启动所有的组件,关闭也是类似,这就是单一启动/关闭机制。
通过对源码的阅读,不仅需要知道代码是怎么设计的,还需要知道代码为什么这样设计,例如本文中提到的LifecycleBase
中的两个属性:lifecycleListeners
和state
,lifecycleListeners
为什么要定义为CopyOnWriteArrayList
类型?state
为什么要用volatile
关键字修饰?结合它们的使用,便可以知道它们的使用场景,可以更加透彻的分析出整个流程执行的机制,从而也会收获的更多。
最后,希望对大家有所帮助。