目录
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 各组件的组成以及工作原理,各组件的层次如图所示。
这张图表示除了各组件间的关系,但是如果想让这个系统运行起来,需要逐一的去初始化、创建、运行这些组件,然后它们在协调的运行,才能对外提供服务。同样在销毁时,也需要销毁这些组件,释放相应的资源。也就是说 Tomcat 需要动态管理这些组件的生命周期。
那如何统一管理这些组件,包括创建、初始化、启动、运行、停止、销毁?如何做到层析清晰,代码逻辑简单?符合开闭原则呢?
上图中包含的各个组件其实是有关系的,不是独立的,他们有两层关系:
- 第一种关系是组件间有大小关系,大组件管理小组件,比如 Server 管理 Service,Service管理 Connector 和 容器,Connector 又管理连接器中的各个组件,容器管理Engine、Host等各个组件。
- 第二种关系是组件有内外,外层组件控制内层组件,比如连接器是外层组件,负责对外交流,外层组件调用内层组件完成业务处理,即请求的过程是又外层组件驱动的。
那么根据上面的两条,我们知道,有小才有大,有内才有外。这也就是整个层级的加载顺序,先加载小组件再加载大组件,先加载内层组件再加载外层组件。此时我们应该就明白了Tomcat 是如何做到一键式启停的了。通过层级结构,加载的优先级。层层迭代进行启动。而停止和启动差不多,也是层层迭代进行停止。
为了解决这个问题,我们需要找到一种通用的、统一的方法来管理组件的生命周期。
Lifecyce接口
系统设计就是要找到变化点和不变点。这里的不变点事每个组件都要经过创建、初始化、启动着几个步骤。这些状态以及状态转化是不变的。而变化点事每个组件创建、初始化、启动等方法具体逻辑不通。
因此,我们把不变点抽象出来成为一个接口,这个接口和生命周期有关,叫做 Lifecycle。接口里定义了几个方法:init、stat、stop、destory,每个具体的组件去实现这些方法。
在父组件的 init 方法里需要创建子组件并调用子组件的 init 方法。同样道理,父组件的 start 方法也需要调用子组件的 start 方法,因此调用者可以调用各个组件的 init 和 start 方法,这就是组合模式,并且只要调用了最顶层,整个 Tomcat 就能启动起来。
下面看下 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 事件,如果有监听器在监听这个事件,它的方法就会被调用。
启动类分析
作为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 中相应的方法,实现一键启动。下图为部分代码:
关于Tomcat一键启动就讨论到这里,欢迎留言讨论。