上一篇《拆解Tomcat10: (四) 图解架构》分享了Tomcat的核心组件及其架构关系,本章接着讲解组件的Tomcat的初始化过程,这么多组件是如何统一加载、启动的。这其中用到了哪些设计模式和设计原则。
1. Server的初始化
在Catalina.createStartDigester()
方法中指定了由哪些实现类去实现对应的接口,那么这些组件是如何实现Lifeycle接口的?
1.1 总览生命周期接口的实现方式
下图在上一篇图二的基础上补充了实现逻辑,见下图中的紫色部分(仅用于展示结构关系,未画所有Lifeycle相关组件):
图一
在Lifeycle接口中,定义了初始化(init)、启动(start)、停止(stop)、销毁(destory)、获取当前状态(getState)等方法,从Lifeycle的名字也可以知道,这个接口用于定义对象的生命周期,即生老病死的过程。
public interface Lifecycle {
public void init() throws LifecycleException;
public void start() throws LifecycleException;
public void stop() throws LifecycleException;
public void destroy() throws LifecycleException;
public LifecycleState getState();
public String getStateName();
}
1.2 通用抽象类
直接实现Lifeycle接口的是LifecycleBase类,这是一个抽象类。以其实现init()方法为例:
@Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
// 触发相应状态的事件
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
/**
* 子类实现此方法以执行所需的任何实例初始化。
*
* @throws LifecycleException If the initialisation fails
*/
protected abstract void initInternal() throws LifecycleException;
在LifecycleBase类中有两个对应的init相关方法,首先init()方法Override父类的方法,通过setStateInternal方法触发相应状态的事件(具体后文描述,不是此处重点),然后调用另一个抽象方法initInternal()。
1.3 子类的实现逻辑
这里预留的initInternal()方法是做什么用的呢,看一下StandardServer类中对此方法的具体实现:
@Override
protected void initInternal() throws LifecycleException {
// 执行父级的逻辑
super.initInternal();
// 初始化 utility executor
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
register(utilityExecutor, "type=UtilityExecutor");
// 注册全局字符串缓存注意虽然缓存是全局的,但如果JVM中存在多个服务器(嵌入时可能会发生),那么相同的缓存将以多个名称注册
onameStringCache = register(new StringCache(), "type=StringCache");
// 注册 MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// 注册并初始化 naming resources
globalNamingResources.init();
//此处省略了加载器相关的代码
// 初始化定义的 Services
for (Service service : services) {
service.init();
}
}
可以看到这里是具体Server相关的代码,也就是说,initInternal()是预留给子类实现的,由子类通过重写此方法来实现自己的个性逻辑。
为什么要这样设计呢?这就是模板方法模式。
2. ☆模板方法模式(TEMPLATE METHOD)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
-- 《设计模式:可复用面向对象软件的基础》
结合上一节的例子:
- 由LifecycleBase定义了一个算法骨架,来实现Lifecycle接口的init()方法。这个算法骨架就是模板方法。
- LifecycleBase类是一个通用的类,所以其中的逻辑只能是通用的逻辑。这些逻辑写在init()方法中,即通过setStateInternal方法触发相应状态的事件等功能逻辑。
- 调用一个抽象方法initInternal(),这个方法交由子类去重写,来实现具体业务逻辑。
2.1 总结一下模板方法模式适用的场景:
- 一个算法逻辑由多个类实现,不同的类之间的逻辑有通用的也有个性化的。
- 创建一个抽象类,完成算法骨架。
- 抽象类中实现通用逻辑,并调用一个空方法,具体逻辑交由子类实现。
- 继承抽象类的子类实现自身的个性化逻辑。
2.2 这样做的好处:
- 代码复用,子类不用再写一遍通用的逻辑。
- 定义了算法骨架,对算法实现进行了约束。
- 权限隔离,抽象类和子类可能由不同角色完成,子类的修改不会影响通用逻辑,也就不会影响其他子类。
类图如下:
图二(引自《设计模式:可复用面向对象软件的基础》)
这也是非常惯例的一种实现方式,如果一个接口会被多个不同类实现,那么常见的操作就是使用一个类去实现这个接口,在这个实现类中编写通用的方法,并调用需要子类实现的抽象方法。子类直接或间接继承这个抽象类,并根据自身需要实现具体逻辑。
3. 所有组件的Init方法传递
继续上一篇的Catalina类的load()方法,此时完成了对Server.xml文件的解析,并将其赋值给了Catalina的server属性。接下来就是调用getServer().init();
方法进行初始化。Server组件作为最上层组件,我们已经知道了其初始化是如何进行的,那么其他子组件是如何统一 管理的呢?
看一下第一节中StandardServer类的initInternal()方法的代码,在最后一部分通过循环遍历的方式调用了所有Service的init方法。
// 初始化定义的 Services
for (Service service : services) {
service.init();
}
同理,由图一可知,Service同样是继承了LifecycleBase类,所以Service和Server的上层通用逻辑是一样的,那么看一下StandardService的initInternal()方法:
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
//Engine 初始化
if (engine != null) {
engine.init();
}
// 初始化 Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// 初始化 mapper listener
mapperListener.init();
// 初始化 our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
connector.init();
}
}
}
其中进行了Engine、Executors、mapperListener、Connectors的初始化,以此类推,其他的组件的initInternal()方法我们就不逐一研究了,因为此处暂不关心这几个组件的功能,仅关注组件之间的关系和调用逻辑。具体每个组件的功能及处理逻辑在单独学习该组件的时候再进行分析。
大概流程如下(只画了部分组件):
图三
4. 总体的调用流程
通过init方法的例子,我们可以大概明白Tomcat的这些核心组件之间的初始化流程。其实对于Lifecycle接口的其他生命周期方法也是类似的,启动(start)、停止(stop)、销毁(destory)等方法也是这样从根节点逐级传递到叶子节点的。也同样存在对应的startInternal()、stopInternal()、destroyInternal()方法。
当然,并不是每个组件都会重写这些实际的生命周期方法 XXXInternal(), 比如 StandardHost就没有重写 initInternal()方法,但重写了startInternal()方法。 这完全由按照逻辑需要决定,反过来说,这也给后期逻辑扩展预留了位置。
所以,对于Tomcat的启动停止等操作,实际上逻辑是这样的:
图四
5. Bootstrap、Catalina与组件的生命周期方法的对应关系
用户对Tomcat发出Start、Stop等指令,Tomcat的Bootstrap中的main方法接到对应的指令参数后,通过反射方式调用Catalina的对应方法,再由Catalina调用Server的对应方法。至此,这个核心组件树由根部开始逐步调用下一级的对应方法,直至叶子节点。
用户指令参数、Catalina的方法以及核心组件的生命周期方法的对应关系如下图:
图五