目录

Tomcat 一键启停

Lifecyce接口

启动类分析


        Apache Tomcat 的一键启停通常是指在 Linux 或 Windows 操作系统中设置便捷的命令或服务,以便快速启动、停止和重启 Tomcat 服务器。

        在没有 SpringBoot 内嵌 Tomcat 之前,都是将项目打为 War 包放在 Tomcat 的 webapp 目录下面,然后如果是 Linux 系统,运行命令 start.sh、如果是 Windows 系统,运行命令start.bat 以后就能启动起来并访问到页面。如果是想要停止运行只需要运行 shutdown.sh或者 shutdown.bat 就能将程序停止起来,那么 Tomcat 是如何做到只需要一个命令就将所有容器启动起来呢?       

Tomcat 一键启停

        上篇文章已经介绍了 Tomcat 各组件的组成以及工作原理,各组件的层次如图所示。

linux redis 开机自动启动_tomcat

        这张图表示除了各组件间的关系,但是如果想让这个系统运行起来,需要逐一的去初始化、创建、运行这些组件,然后它们在协调的运行,才能对外提供服务。同样在销毁时,也需要销毁这些组件,释放相应的资源。也就是说 Tomcat 需要动态管理这些组件的生命周期。

        那如何统一管理这些组件,包括创建、初始化、启动、运行、停止、销毁?如何做到层析清晰,代码逻辑简单?符合开闭原则呢?

        上图中包含的各个组件其实是有关系的,不是独立的,他们有两层关系:

  1. 第一种关系是组件间有大小关系,大组件管理小组件,比如 Server 管理 Service,Service管理 Connector 和 容器,Connector 又管理连接器中的各个组件,容器管理Engine、Host等各个组件。
  2. 第二种关系是组件有内外,外层组件控制内层组件,比如连接器是外层组件,负责对外交流,外层组件调用内层组件完成业务处理,即请求的过程是又外层组件驱动的。

        那么根据上面的两条,我们知道,有小才有大,有内才有外。这也就是整个层级的加载顺序,先加载小组件再加载大组件,先加载内层组件再加载外层组件。此时我们应该就明白了Tomcat 是如何做到一键式启停的了。通过层级结构,加载的优先级。层层迭代进行启动。而停止和启动差不多,也是层层迭代进行停止。

        为了解决这个问题,我们需要找到一种通用的、统一的方法来管理组件的生命周期。

Lifecyce接口

        系统设计就是要找到变化点和不变点。这里的不变点事每个组件都要经过创建、初始化、启动着几个步骤。这些状态以及状态转化是不变的。而变化点事每个组件创建、初始化、启动等方法具体逻辑不通。

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

        在父组件的 init 方法里需要创建子组件并调用子组件的 init 方法。同样道理,父组件的 start 方法也需要调用子组件的 start 方法,因此调用者可以调用各个组件的 init 和 start 方法,这就是组合模式,并且只要调用了最顶层,整个 Tomcat 就能启动起来。

        下面看下 Lifecycle 源码

linux redis 开机自动启动_初始化_02

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

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

linux redis 开机自动启动_tomcat_03

        具体来说就是在 Lifecycle 接口中增加两个方法:添加监听器和删除监听器。除此之外,我们还需要定义一个 Enum 来表示组件有哪些状态,以及处在什么状态会触发什么事件。因此Lifecycle 和 LifecycleState 就被定义在了下面这样。

linux redis 开机自动启动_tomcat_04

        组件的生命周期有 NEW、INITIALIZING、INITIALIZED、STARTING_PREP、STARTING、STARTED 等,而组件一旦到达相应的状态就会触发相应的事件,比如 NEW 状态表示组件刚刚被实例化;而 init 方法被调用时,状态就变成 INITIALIZING 状态,这时,就会触发BEFORE_INIT_EVENT 事件,如果有监听器在监听这个事件,它的方法就会被调用。

启动类分析

        作为Tomcat的入口类,我们先看看Bootstrap中做了什么。先看下main方法中重要的代码。

public static void main(String args[]) {

        synchronized (daemonLock) {
            if (daemon == null) {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try {
                    bootstrap.init();
                } catch (Throwable t) {
                    handleThrowable(t);
                    t.printStackTrace();
                    return;
                }
                daemon = bootstrap;
            } else {
                // When running as a service the call to stop will be on a new
                // thread so make sure the correct class loader is used to
                // prevent a range of class not found exceptions.
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
        }

        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

        根据脚本中传入的不同命令,调用 Catalina 不同的方法。由于我们主要分析的 Tomcat 如何做到一键式启停的,所以我们主要分析 Catalina 的 start 方法。

        在init方法中指定了启动类为:org.apache.catalina.startup.Catalina,Catalina 中通过 start 方法获取 Server,然后再调用 Server 的 start 方法,这样就能调用到 Lifecycle 中相应的方法,实现一键启动。下图为部分代码:

linux redis 开机自动启动_java_05

关于Tomcat一键启动就讨论到这里,欢迎留言讨论。