07丨Tomcat如何实现一键式启停

文章详细介绍了Tomcat如何通过LifeCycle接口和LifeCycleBase抽象基类实现组件的一键式启停,强调了组件创建的顺序原则和可扩展性的设计,包括使用观察者模式处理组件状态变化的事件,以及通过基类实现代码复用,确保系统的可维护性和可扩展性。
摘要由CSDN通过智能技术生成

07 | Tomcat如何实现一键式启停?

相信你对 Tomcat 的架构已经有所了解,知道了 Tomcat 都有哪些组

件,组件之间是什么样的关系,以及 Tomcat 是怎么处理一个 HTTP 请求的。下面我们通

过一张简化的类图来回顾一下,从图上你可以看到各种组件的层次关系,图中的虚线表示一

个请求在 Tomcat 中流转的过程。

在这里插入图片描述

上面这张图描述了组件之间的静态关系,如果想让一个系统能够对外提供服务,我们需要创

建、组装并启动这些组件;在服务停止的时候,我们还需要释放资源,销毁这些组件,因此

这是一个动态的过程。也就是说,Tomcat 需要动态地管理这些组件的生命周期。

在我们实际的工作中,如果你需要设计一个比较大的系统或者框架时,你同样也需要考虑这

几个问题:如何统一管理组件的创建、初始化、启动、停止和销毁?如何做到代码逻辑清

晰?如何方便地添加或者删除组件?如何做到组件启动和停止不遗漏、不重复?

今天我们就来解决上面的问题,在这之前,先来看看组件之间的关系。如果你仔细分析过这

些组件,可以发现它们具有两层关系。

第一层关系是组件有大有小,大组件管理小组件,比如 Server 管理 Service,Service 又

管理连接器和容器。

第二层关系是组件有外有内,外层组件控制内层组件,比如连接器是外层组件,负责对外

交流,外层组件调用内层组件完成业务功能。也就是说,请求的处理过程是由外层组件来

驱动的。

这两层关系决定了系统在创建组件时应该遵循一定的顺序。

第一个原则是先创建子组件,再创建父组件,子组件需要被“注入”到父组件中。

第二个原则是先创建内层组件,再创建外层组件,内层组建需要被“注入”到外层组件。

因此,最直观的做法就是将图上所有的组件按照先小后大、先内后外的顺序创建出来,然后

组装在一起。不知道你注意到没有,这个思路其实很有问题!因为这样不仅会造成代码逻辑

混乱和组件遗漏,而且也不利于后期的功能扩展。

为了解决这个问题,我们希望找到一种通用的、统一的方法来管理组件的生命周期,就像汽

车“一键启动”那样的效果。

一键式启停:LifeCycle 接口

我在前面说到过,设计就是要找到系统的变化点和不变点。这里的不变点就是每个组件都要

经历创建、初始化、启动这几个过程,这些状态以及状态的转化是不变的。而变化点是每个

具体组件的初始化方法,也就是启动方法是不一样的。

因此,我们把不变点抽象出来成为一个接口,这个接口跟生命周期有关,叫作 LifeCycle。

LifeCycle 接口里应该定义这么几个方法:init()、start()、stop() 和 destroy(),每个具体

的组件去实现这些方法。

理所当然,在父组件的 init() 方法里需要创建子组件并调用子组件的 init() 方法。同样,在

父组件的 start() 方法里也需要调用子组件的 start() 方法,因此调用者可以无差别的调用各

组件的 init() 方法和 start() 方法,这就是组合模式的使用,并且只要调用最顶层组件,也

就是 Server 组件的 init() 和 start() 方法,整个 Tomcat 就被启动起来了。下面是

LifeCycle 接口的定义。

在这里插入图片描述

可扩展性:LifeCycle 事件

我们再来考虑另一个问题,那就是系统的可扩展性。因为各个组件 init() 和 start() 方法的

具体实现是复杂多变的,比如在 Host 容器的启动方法里需要扫描 webapps 目录下的

Web 应用,创建相应的 Context 容器,如果将来需要增加新的逻辑,直接修改 start() 方

法?这样会违反开闭原则,那如何解决这个问题呢?开闭原则说的是为了扩展系统的功能,

你不能直接修改系统中已有的类,但是你可以定义新的类。

我们注意到,组件的 init() 和 start() 调用是由它的父组件的状态变化触发的,上层组件的

初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动,因此我们把组件的生

命周期定义成一个个状态,把状态的转变看作是一个事件。而事件是有监听器的,在监听器

里可以实现一些逻辑,并且监听器也可以方便的添加和删除,这就是典型的观察者模式

具体来说就是在 LifeCycle 接口里加入两个方法:添加监听器和删除监听器。除此之外,我

们还需要定义一个 Enum 来表示组件有哪些状态,以及处在什么状态会触发什么样的事

件。因此 LifeCycle 接口和 LifeCycleState 就定义成了下面这样。
在这里插入图片描述

从图上你可以看到,组件的生命周期有 NEW、INITIALIZING、INITIALIZED、

STARTING_PREP、STARTING、STARTED 等,而一旦组件到达相应的状态就触发相应的

事件,比如 NEW 状态表示组件刚刚被实例化;而当 init() 方法被调用时,状态就变成

INITIALIZING 状态,这个时候,就会触发 BEFORE_INIT_EVENT 事件,如果有监听器在监

听这个事件,它的方法就会被调用。

重用性:LifeCycleBase 抽象基类

有了接口,我们就要用类去实现接口。一般来说实现类不止一个,不同的类在实现接口时往

往会有一些相同的逻辑,如果让各个子类都去实现一遍,就会有重复代码。那子类如何重用

这部分逻辑呢?其实就是定义一个基类来实现共同的逻辑,然后让各个子类去继承它,就达

到了重用的目的。

而基类中往往会定义一些抽象方法,所谓的抽象方法就是说基类不会去实现这些方法,而是

调用这些方法来实现骨架逻辑。抽象方法是留给各个子类去实现的,并且子类必须实现,否

则无法实例化。

比如宝马和荣威的底盘和骨架其实是一样的,只是发动机和内饰等配套是不一样的。底盘和

骨架就是基类,宝马和荣威就是子类。仅仅有底盘和骨架还不是一辆真正意义上的车,只能

算是半成品,因此在底盘和骨架上会留出一些安装接口,比如安装发动机的接口、安装座椅

的接口,这些就是抽象方法。宝马或者荣威上安装的发动机和座椅是不一样的,也就是具体

子类对抽象方法有不同的实现。

回到 LifeCycle 接口,Tomcat 定义一个基类 LifeCycleBase 来实现 LifeCycle 接口,把一

些公共的逻辑放到基类中去,比如生命状态的转变与维护、生命事件的触发以及监听器的添

加和删除等,而子类就负责实现自己的初始化、启动和停止等方法。为了避免跟基类中的方

法同名,我们把具体子类的实现方法改个名字,在后面加上 Internal,叫 initInternal()、

startInternal() 等。我们再来看引入了基类 LifeCycleBase 后的类图:

在这里插入图片描述

从图上可以看到,LifeCycleBase 实现了 LifeCycle 接口中所有的方法,还定义了相应的抽

象方法交给具体子类去实现,这是典型的模板设计模式

我们还是看一看代码,可以帮你加深理解,下面是 LifeCycleBase 的 init() 方法实现。

在这里插入图片描述

这个方法逻辑比较清楚,主要完成了四步:

第一步,检查状态的合法性,比如当前状态必须是 NEW 然后才能进行初始化。

第二步,触发 INITIALIZING 事件的监听器:

 复制代码

1 setStateInternal(LifecycleState.INITIALIZING, null, false);

在这个 setStateInternal 方法里,会调用监听器的业务方法。

第三步,调用具体子类实现的抽象方法 initInternal() 方法。我在前面提到过,为了实现一

键式启动,具体组件在实现 initInternal() 方法时,又会调用它的子组件的 init() 方法。

第四步,子组件初始化后,触发 INITIALIZED 事件的监听器,相应监听器的业务方法就会

被调用。

 复制代码

1 setStateInternal(LifecycleState.INITIALIZED, null, false);

总之,LifeCycleBase 调用了抽象方法来实现骨架逻辑。讲到这里, 你可能好奇,

LifeCycleBase 负责触发事件,并调用监听器的方法,那是什么时候、谁把监听器注册进来

的呢?

分为两种情况:

Tomcat 自定义了一些监听器,这些监听器是父组件在创建子组件的过程中注册到子组件

的。比如 MemoryLeakTrackingListener 监听器,用来检测 Context 容器中的内存泄

漏,这个监听器是 Host 容器在创建 Context 容器时注册到 Context 中的。

我们还可以在 server.xml 中定义自己的监听器,Tomcat 在启动时会解析 server.xml,

创建监听器并注册到容器组件。

生周期管理总体类图

通过上面的学习,我相信你对 Tomcat 组件的生命周期的管理有了深入的理解,我们再来

看一张总体类图继续加深印象。

在这里插入图片描述

这里请你注意,图中的 StandardServer、StandardService 等是 Server 和 Service 组件

的具体实现类,它们都继承了 LifeCycleBase。

StandardEngine、StandardHost、StandardContext 和 StandardWrapper 是相应容器

组件的具体实现类,因为它们都是容器,所以继承了 ContainerBase 抽象基类,而

ContainerBase 实现了 Container 接口,也继承了 LifeCycleBase 类,它们的生命周期管

理接口和功能接口是分开的,这也符合设计中接口分离的原则

级节点的注册方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员zhi路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值