介绍
该笔记是在学习拉勾教育 Java 高薪训练营后,结合课程和老师的视频,自己跟踪源码后做的笔记。
文章目录
- Tomcat 的启动化过程分析(一),介绍 Tomcat 的整体结构和启动流程;
- Tomcat 的启动化过程分析(二),使用反射创建实例 Catalina;
- Tomcat 的启动化过程分析(三),调用 Catalina.load(),进行一键初始化;
- Tomcat 的启动化过程分析(四),调用 Catalina.start(),进行一键启动;
Tomcat 的架构
Tomcat 在启动过程前,会先进行初始化,通过解析 server.xml 来创建一个多层的容器。这个容器由多个组件组成,以 server.xml 为例,这个 xml 文件包含了要启动的各组件。
<Server>
<Service>
<Connector />
<Connector />
<Engine>
<Host>
<Context />
</Host>
</Engine>
</Service>
</Server>
- 最顶层组件为 Server, Tomcat 在初始化时会首先创建一个 Server。通过创建 Digester 来解析 xml 文件,使用反射创建 StandardServer(默认的 server);
- 顶层组件为 Service,一个 Server 可以有多个 Service,在代码里 StandardServer#initInternal 可看到会遍历每个 Service 的 init() 方法,在 init() 方法中每个 Service 实现自己的 initInternal() 方法。达到初始化一个 Server 时,会初始化一个或多个 Service。每个 Service 包含多个连接器和一个容器。Service 的主要作用是将连接器和容器包在一起,每个 Service 对应不同的端口号,即对应不同的应用。一个应用监听一个端口号,否则会有端口冲突,导致传过来的数据包不知道给哪个端口号;
- Connector 为连接器组件。连接器负责网络通信,解析应用层协议,并使用适配器模式将 Request / Response 转换为 ServletRequest/ ServletResponse。有多个连接器,是因为不同连接器解析不同协议和支持不同的 I/O 模型,协议有 HTTP 1.1、HTTP 2 ,I/O 模型有 NIO、IO 等;
- Engine 为容器组件,表示引擎,用来管理多个虚拟站点,一个 Server 只有一个 Engine。以 StandardServcie#initInternal 为例,使用 engine.init() 对其初始化,这里 engine 为 StandardEngine。容器有四个组件组成,用于接收连接器传来的 ServletRequest/ ServletResponse 进行处理,处理完后在发给连接器,让它发送给对应的客户端;
- Host 为容器组件,表示虚拟主机(域名)。一个 Service 有多个 Host,即多个域名。比如有 localhost、https://manage.shopping.com/ 、https://user.shopping.com/ 等;
- Context 为容器组件,表示一个 Web 应用程序,处理请求;
- Wrapper 为容器组件,表示一个 Servlet。一个 Context 中有多个 Servlet,处理请求。
图片来自深入拆解Tomcat & Jetty,一个 Tomcat(即 Server)实例包含多个 Service,每个 Service 对应不同的应用,监听不同的端口。一个 Service 包含多个连接器和一个容器。连接器和容器间通过 ServletRequest 和 ServletRespone 进行通信。
Tomcat 的一键启动
从前面的结构介绍可看出,Tomcat 的容器是存在父子关系的,一键启动是通过启动父组件,由父组件来启动一系列子组件完成的。
各组件会继承 LifeCycle 接口的方法,同样状态所有组件调用相同方法,比如初始化,Server.init()、Service.init()、Engine.init() 等都调用 init 方法。同理,启动时,各组件也是调用同样的 start 方法。
Tomcat 的启动过程
反射创建实例 Catalina,调用该实例 Catalina 的初始化 load() 和启动 start() 方法。这两方法会调用各组件的方法,进行一键初始化和一键启动。
- bootstrap.init(),反射创建实例 Catalina;
- daemon.load(),daemon 为 bootstrap。初始化,会调用 Catalina.load() 方法,接着会调用各组件的初始化方法 initInternal,达到一键初始化;
- daemon.start(),调用 Catalina.start() 方法,接着调用各组件的 start 方法,达到一键启动。
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);
}
}