Tomcat 是如何实现一键启停的

如果大家觉得文章有错误内容,欢迎留言或者私信讨论~

  首先让我们回顾一下 Tomcat 的整体架构,从下图你可以看到 Tomcat 中各种组件的层次关系,图中的虚线表示一个请求在 Tomcat 中流转的过程。

在这里插入图片描述

  上图描述了 Tomcat 的组件之间的静态关系,如果让一个系统能够对外提供服务,我们需要创建、组装启动这些组件,并且在服务停止的时候,能够释放资源、销毁这些组件,也就是说这是一个动态的过程,Tomcat 也需要动态管理这些组件的生命周期。

  在我们的实际开发中,如果我们也要设计一个比较大的系统或者框架的时候,同样需要考虑这几个问题:如何统一管理组件的创建、初始化、启动、停止和销毁?如何做到代码逻辑清晰?如果方便的添加和删除组件?如何确定组件的启动和停止不遗漏、不重复?

  这就是今天的主要问题。在这之前,先来看看组件之间的关系。如果你仔细分析过这些组件,可以发现它们具有两层关系。

  • 第一层关系是组件有大有小,大组件管理小组件,比如 Server 管理 Service,Service 又管理连接器和容器
  • 第二次关系是组件有外有内,比如连接器是外层组件,负责对外交流,外层组件调用内层组件完成业务功能。也就是说,请求的处理过程是由外层组件来驱动的。

  这两层关系决定了系统启动时是有循序的,先创建子组件、内层组件,随后将它们 “注入” 到父组件、外层组件中。

  最直观的做法就是将图上的组件按照先后顺序创建出来然后组装到一起。但是这样反而会让代码混乱和组件遗漏,也让后期的拓展变得困难。那么 Tomcat 是如何做的呢?

Lifecycle 接口

  做系统设计的时候,要去挖掘一个系统的不变点和变化点。这里的不变点就是每个组件都要经历创建、初始化、启动以及这几个状态的流转是不变的,变化的是每个具体的组件的创建、初始化、启动方法的具体实现是不一样的。

  因此我们把不变的点抽象出来成为一个接口,这个接口跟生命周期有关,叫作 Lifecycle。Lifecycle 接口里应该定义这么几个方法:init、start、stop 和 destroy,每个具体的组件去实现这些方法。

  也就是说,在父组件的 init 方法需要创建子组件并且调用子组件的 init 方法,start 方法也需要调用子组件的 start 方法,这就是组合模式的应用,子类实现父类的接口,并且在内部组装和使用。也就是说只要调用 Server 组件的 init 和 start 方法,整个 Tomcat 就启动起来了,下图是 Lifecycle 接口的定义。

在这里插入图片描述

Lifecycle 接口的可拓展性

  我们再来考虑另一个问题,也就是系统的可拓展性。因为各个组件 init 和 start 方法的具体实现是复杂多变的,比如在 Host 容器的启动方法里需要扫描 webapps 目录下的 Web 应用,创建相应的 Context 容器,如果将来需要增加新的逻辑,直接修改 start 方法?这样会违反开闭原则,那如何解决这个问题呢?开闭原则说的是为了扩展系统的功能,你不能直接修改系统中已有的类,但是你可以定义新的类。

  我们注意到组件的 init 和 start 调用是由它的父类的状态变化触发的,上层组件的初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动,因此我们把组件的生命周期定义成一个个状态,把状态的转变看作是一个事件。而事件是有监听器的,在监听器里可以实现一些逻辑,并且监听器也可以方便的添加和删除,这就是典型的观察者模式

  具体来说就是在 Lifecycle 接口里加两个方法:增加和删除监听器,以及定义一个枚举 Enum 确定状态的变化有哪些。因此 Lifecycle 接口和 LifecycleState 就定义成了下面这样:

在这里插入图片描述

  从图上你可以看到组件的各种生命周期状态,而一旦组件到达相应的状态就触发相应的事件,比如 NEW 状态表示组件刚刚被实例化;而当 init 方法被调用时,状态就变成 INITIALIZING 状态,这个时候,就会触发 BEFORE_INIT_EVENT 事件,如果有监听器在监听这个事件,它的方法就会被调用。

Lifecycle 接口的可重用性

  有了接口,我们就要用类去实现接口,一般来说实现类不止一个,并且每个实现类或多或少都会有一些相同的逻辑,那么就会有重复代码,如何搞定这些重复代码呢?就需要用到**模板模式,**就是定义一个基类来实现这些相同的逻辑。

  Tomcat 定义一个基类 LifecycleBase 来实现 Lifecycle 接口,把一些公共的逻辑放到基类中去,比如生命状态的转变与维护、生命事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等方法。为了避免跟基类中的方法同名,我们把具体子类的实现方法改个名字,在后面加上 Internal,叫 initInternal、startInternal 等。我们再来看引入了基类 LifecycleBase 后的类图:

在这里插入图片描述

  顺便让我们看一下代码加深一下理解:

// 逻辑非常清晰
@Override
public final synchronized void init() throws LifecycleException {
    
    
    //1. 状态检查,确定当前状态必须是 NEW 然后才能初始化
    if (!state.equals(LifecycleState.NEW)) {
    
    
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
    
    
        //2.触发INITIALIZING事件的监听器
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        
        //3.调用具体子类的初始化方法(子类的具体的 initInternal 方法)
        initInternal();
        
        //4. 触发INITIALIZED事件的监听器
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
    
    
      ...
    }
}

  你可能好奇 LifecycleBase 负责触发事件,并调用监听器的方法,那是什么时候、谁把监听器注册进来的呢?分两种情况:

  1. Tomcat 自身定义的一些监听器,这些监听器是父组件在创建子组件的过程中注册到子组件的。比如 MemoryLeakTrackingListener 监听器,用来检测 Context 容器中的内存泄漏,这个监听器是 Host 容器在创建 Context 容器时注册到 Context 中的。
  2. 我们还可以在server.xml中定义自己的监听器,Tomcat 在启动时会解析server.xml,创建监听器并注册到容器组件。

总结

  为了加深对 Tomcat 组件的生命周期管理,来看一张总体类图来加深印象:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43654226/article/details/127252816