tomcat启动流程简要分析

下面的流程就是跟着走了一遍代码,枯燥无味。

说明

文中大部分都是各处摘抄,做一个记录。顺序没有调整,文章的最后才去介绍了生命周期

Tomcat 框架介绍

https://blog.nowcoder.net/n/0c4b545949344aa0b313f22df9ac2c09
详细描述了tomcat的架构和各部分组件作用

Tomcat的整体结构如下:

tomcat

tomcat其实就是http服务器+servlet容器,tomcat能解析客户端传来的http/https报文,并将其转发至对应的servlet容器去处理,处理之后的结果封装成http/https报文返回给客户端。

Server:Tomcat服务器,一个Tomcat的实例,服务器启动会自行加载配置文件中配置好的连接器与容器。

Service:一个Server可以运行多个Service,但是默认只有一个。


Connector:连接器用于解析不同的协议(HTTP、HTTPS、AJP),对于Servlet来说是无法感觉到协议的不同的,以为连接器将协议解析并提供一个ServletRequest对象,Servlet只能看到ServletRequest对象,Servlet返回的内容封装到了ServletResponse对象中,由连接器转换为协议返还给客户端。一个Service中可以有多个connector,多个connector对应一个container。

image-20240320132501328

为了实现这些功能,Tomcat又实现了3个组件,分别是EndPoint、Processor 和 Adapter。网络通信的 I/O 模型是变化的, 应用层协议也是变化的,但是整体的处理逻辑是不变的,EndPoint 负责提供字节流给 ProcessorProcessor负责提供 Tomcat Request 对象给 AdapterAdapter负责提供 ServletRequest对象给容器。总结下来,连接器的三个核心组件 EndpointProcessorAdapter来分别做三件事情,其中 EndpointProcessor放在一起抽象成了 ProtocolHandler组件,它们的关系如下图所示。

连接器

EndPoint是用来实现TCP/IP协议的数据读写的,本质调用操作系统的socket接口。在EndPoint的具体实现类中,出现了两个重要组件:Acceptor、SocketProcessor。其中,Acceptor用于接听Socket连接请求。SocketProcessor用于处理 Acceptor 接收到的 Socket请求,它实现 Runnable接口,在 Run方法里调用应用层协议处理组件 Processor 进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行。Adapter将处理后的请求转发给对应的Engine容器进行下一步处理。


Container:Container表示容器,包括Engine、Host、Context、Wrapper。

一个Engine容器中有多个Host容器,一个Host容器中又有多个Context容器,一个Context中又有多个Wrapper容器。换种说法,Wrapper 表示一个 ServletContext 表示一个 Web 应用程序,而一个 Web 程序可能有多个 ServletHost 表示一个虚拟主机(内部可以搭建多个web应用),一个 Tomcat 可以配置多个站点(Host);一个站点( Host) 可以部署多个 Web 应用;Engine 代表 引擎,用于管理多个站点(Host),一个 Service 只能有 一个 Engine。可以通过配置细致了解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<Server port="8005" shutdown="SHUTDOWN"> // 顶层组件,可包含多个 Service,代表一个 Tomcat 实例

<Service name="Catalina"> // 顶层组件,包含一个 Engine ,多个连接器
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> // 连接器

// 容器组件:一个 Engine 处理 Service 所有请求,包含多个 Host
<Engine name="Catalina" defaultHost="localhost">
// 容器组件:处理指定Host下的客户端请求, 可包含多个 Context
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
// 容器组件:处理特定 Context Web应用的所有客户端请求
<Context></Context>
</Host>
</Engine>
</Service>
</Server>

一个请求如何定位到 Servlet

一个请求是如何定位到让哪个 WrapperServlet 处理的?答案是,Tomcat 是用 Mapper 组件来完成这个任务的。

Mapper 组件的功能就是将用户请求的 URL 定位到一个 Servlet,它的工作原理是:Mapper组件里保存了 Web 应用的配置信息,其实就是容器组件与访问路径的映射关系,比如 Host容器里配置的域名、Context容器里的 Web应用路径,以及 Wrapper容器里 Servlet 映射的路径,你可以想象这些配置信息就是一个多层次的 Map

Mapper 保存的关联关系就是通过观察者模式监听在每个组件启动的时候将对应的关联关系保存的。

当一个请求到来时,Mapper 组件通过解析请求 URL 里的域名和路径,再到自己保存的 Map 里去查找,就能定位到一个 Servlet。请你注意,一个请求 URL 最后只会定位到一个 Wrapper容器,也就是一个 Servlet

img

假如有用户访问一个 URL,比如图中的http://user.shopping.com:8080/order/buy,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?

  1. 首先根据协议和端口号确定 Service 和 Engine。Tomcat 默认的 HTTP 连接器监听 8080 端口、默认的 AJP 连接器监听 8009 端口。上面例子中的 URL 访问的是 8080 端口,因此这个请求会被 HTTP 连接器接收,而一个连接器是属于一个 Service 组件的,这样 Service 组件就确定了。我们还知道一个 Service 组件里除了有多个连接器,还有一个容器组件,具体来说就是一个 Engine 容器,因此 Service 确定了也就意味着 Engine 也确定了。
  2. 根据域名选定 Host。 Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器,比如例子中的 URL 访问的域名是user.shopping.com,因此 Mapper 会找到 Host2 这个容器。
  3. 根据 URL 路径找到 Context 组件。 Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径,比如例子中访问的是 /order,因此找到了 Context4 这个 Context 容器。
  4. 根据 URL 路径找到 Wrapper(Servlet)。 Context 确定后,Mapper 再根据 web.xml 中配置的 Servlet 映射路径来找到具体的 Wrapper 和 Servlet。

​ 对于Tomcat中消息流的流转机制,4个不同级别的容器是通过管道机制进行流转的,对于每个请求都是一层一层处理的。如图所示,当客户端请求到达服务端后,请求被抽象成Request对象后向4个容器进行传递,首先经过Engine容器的管道通过若干阀门,最后通过StandardEngineValve阀门流转到Host容器的管道,处理后继续往下流转,通过StandardHostValve阀门流转到Context容器的管道,继续往下流转,通过StandardContextValve阀门流转到Wrapper容器的管道,而对Servlet的核心处理也正是在StandardWrapperValve阀门中。StandardWrapperValve阀门先由Application FilterChain组件执行过滤器,然后调用Servlet的service方法对请求进行处理,然后对客户端响应。

image-20240321134137438

编写一个Servlet

indexServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.web;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class indexServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter pw = resp.getWriter();
pw.write("<h1>hello!</h1>");
pw.flush();
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}


web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>indexServlet</servlet-name>
<servlet-class>com.web.indexServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>indexServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

配置tomcat启动就行:

image-20240320153056472

servlet初始化与装载流程分析

先尝试调试tomcat_embed_core

pom.xml 如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>servletMemoryShell</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.83</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.83</version>
<scope>compile</scope>
</dependency>
</dependencies>

</project>

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import java.io.File;

public class Main {
public static void main(String[] args) throws LifecycleException {
Tomcat tomcat = new Tomcat();
tomcat.getConnector(); //tomcat 9.0以上需要加这行代码,参考:https://blog.csdn.net/qq_42944840/article/details/116349603
Context context = tomcat.addWebapp("", new File(".").getAbsolutePath());
Tomcat.addServlet(context, "helloServlet", new HelloServlet());
context.addServletMappingDecoded("/hello", "helloServlet");
tomcat.start();
tomcat.getServer().await();
}
}

HelloServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("Hello, World!");
out.println("</body></html>");
}
}

Servlet初始化流程

根据提供的代码首先运行的是addWebapp(),调用流程图如下:

image-20240320163247513

在java代码的addWebapp()处打上断点跟进去
image-20240320165531126在方法运行的时候,首先会运行方法参数上的方法也就是getHost(),然后在getHost的开始又调用了getEngine(),之后调用了getServer(),因为在初始化的Tomcat之后,获取连接器的时候已经创建了service和server,就与上面的流程图所示的一样,getXxxx()会判断是否为null,如果不为null,直接返回,若为null便进行初始化。

image-20240320170105398

image-20240320184124999

image-20240320184405125

image-20240320184520758

初始化之后便进入addWebapp()中,通过反射获取了ContextConfig的Class对象,并构造了该类的实例对象赋给了listener,newInstance()调用了该类的无参构造去实例化对象,在调用newInstance前,所实例化的类必须已经加载到内存中,并已完成连接阶段(验证、准备、解析),之后调用addWebapp的重构方法。

image-20240320190623734

silence()的作用是将指定的日志记录器设置为特定的日志级别,以控制日志输出的详细程度,之后就调用了createContext() 返回了一个StandardContext的实例化对象。

image-20240320192203753

之后会添加xmlListener(从全局的web.xml获取一些配置项)和LifecycleListener到context中,这些操作应该是设置一些包里自带的默认配置。这些都是程序一开始所做的初始化工作。

之后就是实例化一个Servlet,并封装在StandardWrapper.existing属性中,之后将Wrapper添加到context中。

image-20240320194857191

以上分析仅仅是通过ExistingStandardWrapper类添加ServletStandardWrapper有些不同:

  • 对于 StandardWrapper,通常是由 Tomcat 根据配置文件或者程序动态创建的,用于部署开发者编写的 Servlet。
  • ExistingStandardWrapper 则是开发者自己创建的,用于将现有的 Servlet 对象包装成为 Tomcat 可管理的实例。

Tomcat启动分析

下载tomcat源码进行调试,主要参考:

https://www.cnblogs.com/LittleHann/p/17735106.html

https://tomcat.apache.org/download-80.cgi打开之后下载core binary zip文件和source文件。

image-20240321135942026

之后新建一个java maven项目,将pom文件修改为下面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.apache</groupId>
<artifactId>tomcatcode</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.5.1</version>
</dependency>
</dependencies>

</project>
  • 将源码解压目录中的 conf、webappas 直接复制到上面新建的项目根路径下D:\javaCode\debugTomcatSrc
  • 将源码解压目录中的 java、modules 直接复制到 D:\javaCode\debugTomcatSrc\src\main
  • 将Binary解压木马中的 lib 直接复制到 D:\javaCode\debugTomcatSrc\lib

image-20240321141910015

之后就将lib的所有依赖添加到项目中就行:

image-20240321142003083

然后进行运行配置:

image-20240321142649848

然后点击运行

image-20240321142747215

参考:https://juejin.cn/post/6942094150216990757

之后就可以打断点调试了,具体的启动流程如下所示:

启动流程

流程分析

在mian方法中实例化了bootstarp,之后执行bootstrap.init()方法

image-20240321144945758

init()方法中,在这个方法中获取了catalina.base/conf/catalina.properties中的conmmon.loader、server.loader、shared.loader的值,并将该值所指向的目录和添加到repositorys中,之后将目录中所有的jar包用URLClassLoader进行加载,然后初始化了一个org.apache.catalina.startup.Catalina实例catalinaDaemon,并将其parentClassLoader设置为sharedLoader.

image-20240321145147613

image-20240321150919071

image-20240321152130874

之后就是通过反射获取Catalina.load()方法并执行

load方法逻辑

在createStartDigester中为解析\conf\server.xml文档做准备,添加一些解析的规则,在之后的parse()方法中解析xml文档并进行初始化内容。

image-20240321154524960

image-20240321161936364

这个Server就对应着Server.xml的解析内容,如下所示。

image-20240321163012835

image-20240321163234015

image-20240321163328862

这些listener在之后的生命周期状态转换时调用监听器中对应生命周期事件(生命周期状态转换和生命周期事件在下文有介绍),然后初始化Server getServer().init(),之后在initInternal()又会调用Service.init()

image-20240321184113614

在Service.init() 方法中又会调用 initInternal(),之后便又调用了engine.init()

engine初始化

下面继续初始化了Executor、mapperListener、connector,在connector的初始化里继续初始化了protocolHandler和CoyoteAdapter,CoyoteAdaptor组件是一个将Connector和Container适配起来的适配器。上面说到protocolHandler是Endpoint和processor的封装。

connector初始化

adapter初始化

endpoint初始化

直到这里依然没有看到Host容器和Context容器的初始化,之后就会运行Catalina.start()->getServer().start()->startInternal()

image-20240321202418413

image-20240321203611146

image-20240321210832255

engine.start()之后会调用LifecycleBase.startInternal(),LifecycleBase是tomcat统一管理组件生命周期的一个基础类,所有扩展了这个类的子类,都会

各个组件会依次启动,所有扩展的子类也会重写对应抽象类中的抽象方法例如initInternal和startInternal,在每个组件初始化或者启动的时候会自然而然的启动下一个组件,就像树的节点一样。

initInternal的具体实现类

startInternal的具体实现类

所以到现在都没看到Host的init()方法,在standardHost中并没有重写init()和initInternal(),但是在standardHost的父类中实现了initInternal方法,之后会被调用,只是重写了startInternal()

image-20240322164207018

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected void startInternal() throws LifecycleException {

// Set error report valve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if (!found) {
Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardHost.invalidErrorReportValveClass", errorValve), t);
}
}
super.startInternal();
}

在engin.start()中会调用org.apache.catalina.core.ContainerBase#startInternal()方法,在该方法中engine会寻找自己的children,之后会去执行child.start()和startInternal(),standardHost也继承了ContainerBase,而ContainerBase和StandardHost、StandardContext、StandardWrapper都实现了startInternal方法,所以他们又会在startInternal方法中进行调用,因为StandardHost还处在NEW state,所以就会调用init(),之后调用start()。

image-20240322164522342

image-20240322191255090

image-20240322191341510

先调用init初始化然后调用startInternal初始化下一个容器

跟进去Host的init(),之后会进入host的startInternal(),然后是startInternal(),之后会调用super.startInternal()。

image-20240322191842622

image-20240322194017273

进入super.startInternal(),这个children是空的,context不是在这里初始化的。

image-20240323192107373

下面进入setState()这个方法,在这个方法里其实有一个lifeCycle事件监听器,之后就触发相应的事件。

image-20240323193203447

context是在HostConfig.deployDirectory()中初始化的,初始化完之后将其添加到host的child列表中

image-20240407195238815

image-20240407195256142

HostConfig是什么时候调用的?

HostConfig类实现了LifecycleListener接口的,在StandardHost类启动时调用的HostConfig,也就是上面说到的StandardHost.startInternal(),然后在状态变换的时候触发fireLifecycleEvent()然后调用到HostConfig.lifecycleEvent()然后又调用HostConfig.start(),之后调用deployApps(),在该方法中如果网站是以war包的形式放在webapps目录下那就在deployWARs()进行部署,如果网站是以目录形式出现在webapps目录中那就通过deployDirectories()进行部署。

image-20240408140238695

在deployDirectories方法中实例化了一个线程池来添加网站部署任务,并通过for循环去将webapps目录下的网站逐一部署起来。

deployDirectories

DeployDirectory类中实现了Runnable接口,添加到线程池中会执行该类的run方法从而执行org.apache.catalina.startup.HostConfig#deployDirectory()。

image-20240408154326123

通过META-INF/context.xml去初始化一个StandardContext对象,之后会调用host.addChild(context),将context添加到host中。

image-20240407200321641

image-20240408161806081

初始化StandardContext之后,便调用了StandardHost.addChild()->super.addChild(),这里的父类是指 ContainerBase类,在该方法中又会调用addChildInternal(),之后就是调用child.start()

image-20240407201232468

image-20240407201353673

因为child就是StandardContext,之后又调用到了StandardContext.start()->LifecycleBase.start()->StandardContext.startInternal(),在该方法中又调用了fireLifecycleEvent()

image-20240407201539234

image-20240407201908279

然后会调用到ContextConfig中的lifecycleEvent(),和HostConfig一样,最后会初始化wrapper。

image-20240407203049397

这里的Servlets就是conf/web.xml中配置的Servlet(tomcat默认所有网站都会加载的配置),默认配置中有两个Servlet:org.apache.catalina.servlets.DefaultServletorg.apache.jasper.servlet.JspServlet,如果部署的网站中没有配置新的Servlet则将会按照默认的servlet去解析请求。

image-20240408133040032

jspServlet顾名思义就是用来解析jsp文件的,只要文件后缀符合.jsp或者.jspx都会被这个jspServlet所处理,另一个defaultServlet则是处理/根目录的请求,如果自己网站配置新的Servlet去处理url-pattern为/的请求,则会覆盖掉默认的defaultServlet。换句话说就是conf/web.xml适用于所有网站。

image-20240408152836522

在部署下一个网站的时候可以看到,Servlet的数量就不止默认的那两个了。

image-20240408162013625

image-20240408162834816

生命周期统一接口—–Lifecycle

​ Tomcat的组件众多如果一个一个启动实在麻烦,所以就有了这个生命周期统一管理的接口,即使之后动态扩展了组件,只要实现了这个接口就是可以用Lifecycle管理启动、停止、关闭。Tomcat内部架构中各个核心组件有包含与被包含的关系,例如,Server包含Service, Service包含Container和Connector,往下再一层层包含。Tomcat就是以容器的方式来组织整个系统架构的,就像数据结构的树,树的根节点没有父节点,其他节点有且仅有一个父节点,每个父节点有零个或多个子节点。鉴于此,可以通过父容器启动它的子容器,这样只要启动根容器,即可把其他所有容器都启动,达到统一启动、停止、关闭的效果。

​ 作为统一的接口,Lifecycle把所有的启动、停止、关闭、生命周期相关的方法都组织到一起,就可以很方便地管理Tomcat各个容器组件的生命周期。下面是Lifecycle接口详细的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface Lifecycle {
public static final String BEFORE_INIT_EVENT = "before_init";
public static final String AFTER_INIT_EVENT = "after_init";
public static final String START_EVENT = "start";
public static final String BEFORE_START_EVENT = "before_start";
public static final String AFTER_START_EVENT = "after_start";
public static final String STOP_EVENT = "stop";
public static final String BEFORE_STOP_EVENT = "before_stop";
public static final String AFTER_STOP_EVENT = "after_stop";
public static final String AFTER_DESTROY_EVENT = "after_destroy";
public static final String BEFORE_DESTROY_EVENT = "before_destroy";
public static final String PERIODIC_EVENT = "periodic";
public static final String CONFIGURE_START_EVENT = "configure_start";
public static final String CONFIGURE_STOP_EVENT = "configure_stop";

public void addLifecycleListener(LifecycleListener listener);
public LifecycleListener[] findLifecycleListeners();
public void removeLifecycleListener(LifecycleListener listener);
public void init() throws LifecycleException;
public void start() throws LifecycleException;
public void stop() throws LifecycleException;
public LifecycleState getState();
public String getStateName();
}

​ 从上面可以看出,Lifecycle其实就定义了一些状态常量和几个方法,这里主要看init、start、stop三个方法,所有需要被生命周期管理的容器都要实现这个接口,并且各自被父容器的相应方法调用。例如,在初始化阶段,根容器Server组件会调用init方法,而在init方法里会调用它的子容器Service组件的init方法,以此类推。

image-20240329151042623

LifecycleMBeanBase是对LifecycleBase的扩展,LifecycleBase类主要做的事情就是tomcat容器生命周期转换、监听生命周期事件。

生命周期的状态转化

LifecycleState类中定义了tomcat生命周期的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public enum LifecycleState {
NEW(false, null),
INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
STARTING(true, Lifecycle.START_EVENT),
STARTED(true, Lifecycle.AFTER_START_EVENT),
STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
STOPPING(false, Lifecycle.STOP_EVENT),
STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
FAILED(false, null);

private final boolean available;
private final String lifecycleEvent;

private LifecycleState(boolean available, String lifecycleEvent) {
this.available = available;
this.lifecycleEvent = lifecycleEvent;
}
……
}

这里拿Server.init()举一个例子:

image-20240322161450600

​ 在进入init()之前,生命周期的state(状态)是NEW执行具体的initInternal()之前,将生命周期设置为INITIALIZING,在完成初始化之后将设置为INITIALIZED,其他状态类似。如果在生命周期的某个阶段发生意外,则可能经历xx→DESTROYING→DESTROYED。整个生命周期的状态转化情况如下图所示:

image-20240322161916475

生命周期事件监听

​ 如果我们面对这么多状态之间的转换,我们肯定会有这样的需求:我希望在某某状态事情发生之前之后做点什么。Tomcat在这里使用了事件监听器模式来实现这样的功能。一般来说事件监听器需要三个参与者:

  • 事件对象,用于封装事件的信息,在事件监听器接口的统一方法中作为参数使用,一般继承java.util.EventObject类。LifecycleEvent类就是事件对象,继承了EventObject类;LifecycleListener为事件监听器接口,里面只定义了一个方法lifecycleEvent (LifecycleEvent event)。很明显,LifecycleEvent作为这个方法的参数。

image-20240407154943256

  • 事件源,触发事件的源头,不同的事件源会触发不同的事件类型。事件源其实是各个组件,在启动初始化的时候会设置对应的状态,从而触发对应状态的事件。setStateInternal是事件的直接触发方法。

  • 事件监听器,负责监听事件源发出的事件,更确切地说,应该是每当发生事件时,事件源就会调用监听器的统一方法去处理。

image-20240407155600995