Tomcat 版本
- 独立部署的 Tomcat 版本 - 9.0.30
- Spring boot 版本 - 2.2.4.RELEASE
- 内嵌 Tomcat-embed-core - 9.0.30
Tomcat 概念论述
Tomcat 的架构 (也叫做 Catalina),是一个精密的层级结构系统。
- Server - Tomcat 实例,一个 Tomcat 进程即为一个 Server;
- Service - Tomcat 服务,Service 是 Tomcat 提供 Servlet 容器服务的单元,Service 在 Server 内部,一个 Server 可以有多个 Service;
- Connector - 用户请求的主体,是 Servlet 容器与外部用户的交汇部分,Connector 在 Service 内,一个 Service 可以有多个 Connector 用以处理多种请求;
- Engine - Engine 代表 Service 内部的 Servlet 容器,一个 Service 只有一个 Engine;
- Host - Host 代表一个站点,一个 Engine 可以有多个 Host;
- Context - Context 代表一个 app 应用,一个 Engine 可以有多个子工程组成,分配不同的路由;
- Wrapper - Wrapper 即是 Servlet 容器,也代表着某一个 Servlet;
- Container - Servlet 容器,上述的 Engine / Host / Context / Wrapper 严格意义上都是 Container;
- Lifecycle - 生命周期,当有相关的事件发生的时候可以被相关的 LifecycleListener 监听到;
- Global Naming Resources - 全局的配置资源;
- Realm - 权限配置;
- Executor - 资源池。
Tomcat 独立部署版配置文件
/conf/server.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
Apache Tomcat v9.0.30 配置文件
-->
<!-- Server 代表了一个 Tomcat 实例。
className 是 Tomcat 实体类,必须使用 org.apache.catalina.Server 的子类,默认 org.apache.catalina.core.StandardServer;
shutdown 代表关闭的指令;
address 代表关闭指令可以的来源,默认只有本地的关闭指令 Tomcat 才会采用;
port 是 Tomcat 关闭自身的指令接收端口,可以通过设置为 -1 来禁止 -->
<Server port="8005" shutdown="SHUTDOWN" address="localhost" className="org.apache.catalina.core.StandardServer" >
<!-- 监听器配置,监听类必须是 org.apache.catalina.LifecycleListener 的子类,
这里配置的类会根据自己监听的生命周期事件,被相关的 Lifecycle 的子类存入 list 中,当发生了相关事件就执行回调方法 -->
<!-- 用于在服务启动的时候打印日志的监听器 -->
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- 默认开启 apr 监听器 -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- 内存不够的时候调用此监听器,执行一次 full gc -->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<!-- JNDI 全局变量管理监听器 -->
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<!-- 由于线程引用了 threadLocal 中的变量导致内存不够的时候调用此监听器,杀掉线程 -->
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<!-- JNDI 全局资源配置,
此处为一个 UserDatabase 对象,用来存储 tomcat-users.xml 文件中的信息
-->
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!-- Service 代表了一个 Tomcat 服务,一个 Server 可以有多个 Service,
同一个 Server 下的 Service 的 name 必须不同,
className 必须使用 org.apache.catalina.Service 的子类,默认 org.apache.catalina.core.StandardService -->
<Service name="Catalina" className="org.apache.catalina.core.StandardService">
<!-- 连接池配置,可以不做配置,会有一个默认的连接池。
namePrefix 线程名称;
maxThreads 最大线程数,默认 200;
maxQueueSize 任务队列最大值,默认 Integer.MAX;
minSpareThreads 最小的活跃线程,默认 25;
maxIdleTime 线程被销毁之前的存活时间,默认 60000 ms(1 min);
deamon 池内的线程是否是守护线程,默认 true -->
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="200" minSpareThreads="25" maxQueueSize="100000000"
deamon="true" maxIdleTime="60000"/>
<!-- Connector 是相应用户请求的主体,一种 Connector 用于代表一种用户请求,
通俗来说 Connector 是 Container 的前置工作对象,可以通俗理解为 Request 和 Response。
port 监控的端口;
protocol 使用的网络 io 连接器;
connectionTimeout 连接超时时间,单位毫秒;
executor 配置连接池;
sslEnabled 是否允许 https;
maxThreads 最大连接数;
redirectPort 用户用 http 请求某个资源,而该资源本身又被设置了必须要 https 方式访问,会自动重定向到这个端口 -->
<!-- protocol:
org.apache.coyote.http11.Http11NioProtocol http1.1 nio 连接器
org.apache.coyote.http11.Http11Nio2Protocol http1.1 aio 连接器
org.apache.coyote.http11.Http11AprProtocol http1.1 apr 连接器,需要操作系统其它支持
HTTP/1.1 http1.1 协议的自动选择选项,有 apr 就使用 apr,没有就使用 nio
AJP/1.3 ajp1.3 协议的自动选择选项,有 apr 就使用 apr,没有就使用 nio -->
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
redirectPort="8443" SSLEnabled="false" executor="tomcatThreadPool"
maxThreads="150">
<!-- http/2 配置,让这个 Connector 可以兼顾解析 http/2 -->
<!-- <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"/> -->
<!-- 配置 https 证书 -->
<!--
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
certificateChainFile="conf/localhost-rsa-chain.pem"
type="RSA" />
</SSLHostConfig>
-->
</Connector>
<!-- Engine 是 Servlet Container 的最高层级,一个 Service 中只能有一个 Engine。
name 名称;
defaultHost 默认的 host 地址 -->
<Engine name="Catalina" defaultHost="localhost">
<!-- Realm 提供用户名密码的映射,很少使用到,不做展开 -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
</Realm>
<!-- Host 虚拟主机,用以管理一个站点,一个站点可以有多个子项目构成。
name 名称,同一个 Engine 下的 Host 不要重复即可,
className 必须是 org.apache.catalina.Engine 的子类,默认 org.apache.catalina.core.StandardEngine,
appBase 站点下属所有项目配置的目录;
autoDeploy 是否自动扫描 appBase 目录下的所有的项目,如果配置为 true 的话,就可以无需配置 <Context></Context> 了;
unpackWars 自动解压 war 包 -->
<Host name="localhost" appBase="webapps" className="org.apache.catalina.core.StandardEngine"
unpackWARs="true" autoDeploy="true">
<!-- 日志格式配置 -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
<!-- Context 代表一个在 host appBase 路径下的 web 应用,
一般情况下可以不配置,因为 host 会去扫描 appBase 下的所有文件夹找 web.xml。
此处将 /webapps/example 路径下的项目配置成根路由下的项目,
将 /webapps/host-manager 路径下的项目配置成 /manager 路由下的项目,以此类推。 -->
<Context docBase="examples" path="/" />
<Context docBase="host-manager" path="/manager" />
<Context docBase="ROOT" path="/root" />
</Host>
</Engine>
</Service>
</Server>
Tomcat 部分组件源码简读
该部分的解析使用 tomcat-embed-core 进行源码观察。
一 Lifecycle
org.apache.catalina.Lifecycle 是一个接口,org.apache.catalina.util.LifecycleBase 实现了 Lifecycle 接口,而 Server / Service / Engine / Host / Context / Wrapper 等组件都继承了 LifecycleBase,也就是说这些组件在发生生命周期事件的时候都可以被监听器监控。
1 监控事件
event 是 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";
// 组件开始根据配置文件配置自身,在 before_start 之后,start 之前
public static final String CONFIGURE_START_EVENT = "configure_start";
// 组件配置结束,在 stop 之后,after_stop 之前
public static final String CONFIGURE_STOP_EVENT = "configure_stop";
2 组件状态
组件状态被定义在 org.apache.catalina.LifecycleState 中,是一个枚举类。
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; // 是否可控制,如果为 false
private final String lifecycleEvent; // 此状态绑定的监控事件
// ...
}
代码内容很简洁。
3 LifecycleBase
LifecycleBase 是 Lifecycle 的基本实现类,以 init() 方法举例:
// LifecycleBase.class
@Override
public final synchronized void init() throws LifecycleException {
// 调用 init 方法需要确保这个组件的状态是 new,如果不是的话会抛出错误
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
// before_init 监听通知
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 真正的初始化逻辑
initInternal();
// after_init 监听通知
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
与监听器进行交互的核心方法是 addLifecycleListener(...) 与 setStateInternal(...):
// LifecycleBase.class
@Override
public void addLifecycleListener(LifecycleListener listener) {
// lifecycleListeners 是一个存放监听器的列表
lifecycleListeners.add(listener);
}
// LifecycleBase.class
// 此方法用于通知所有的监听器相关逻辑
private synchronized void setStateInternal(
LifecycleState state, Object data, boolean check) throws LifecycleException {
// 记录日志
if (log.isDebugEnabled()) {
log.debug(sm.getString("lifecycleBase.setState", this, state));
}
// 是否启用状态判断
if (check) {
// 传入的状态值的非空判断,非法操作,一般不会出现
if (state == null) {
invalidTransition("null");
return;
}
// 如果状态是如下几种的话,会抛出错误
if (!(state == LifecycleState.FAILED ||
(this.state == LifecycleState.STARTING_PREP
&& state == LifecycleState.STARTING)
|| (this.state == LifecycleState.STOPPING_PREP
&& state == LifecycleState.STOPPING)
|| (this.state == LifecycleState.FAILED
&& state == LifecycleState.STOPPING))
) {
invalidTransition(state.name());
}
}
// 更新状态
this.state = state;
String lifecycleEvent = state.getLifecycleEvent();
if (lifecycleEvent != null) {
// 此处会遍历监听器
fireLifecycleEvent(lifecycleEvent, data);
}
}
// LifecycleBase.class
protected void fireLifecycleEvent(String type, Object data) {
// 遍历监听器列表,执行所有监听器的 lifecycleEvent(...) 方法
LifecycleEvent event = new LifecycleEvent(this, type, data);
for (LifecycleListener listener : lifecycleListeners) {
listener.lifecycleEvent(event);
}
}
4 LifecycleListener
所有实现了 org.apache.catalina.LifecycleListener 接口的类都可以是监听器:
public interface LifecycleListener {
public void lifecycleEvent(LifecycleEvent event);
}
以最简单的 org.apache.catalina.startup.VersionLoggerListener 为例子:
// VersionLoggerListener.class
@Override
public void lifecycleEvent(LifecycleEvent event) {
// Tomcat 中的 Listener 一般都使用 if 判断进行事件的筛选
// Tomcat 没有在接口层面作出更加强制的监听逻辑判断
if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {
// 打印日志
log();
}
}
Spring boot 中与 Servlet 相关的配置
Spring boot 中与此相关的配置比较多,只列举部分笔者在项目中常用的部分。
server:
# 端口
port: 8080
# http 协议头的大小
max-http-header-size: 8KB
# servlet 设置
servlet:
session:
# session 失效时常,默认 30min
timeout: 30m
# 是否持久化 session,持久化之后 session 不会因为服务的重启而丢失
persistent: false
# 如果设置为需要持久化,那么可以指定存储的目录
# store-dir: classpath:session
# 配置 Tomcat 相关的参数
tomcat:
# 最大线程数
max-threads: 2
# 最大连接数,默认 200
max-connections: 10
# encode
uri-encoding: UTF-8
# 连接超时时间
connection-timeout: 2m
# 最大连接排队数
accept-count:
# 提交表单的最大大小
max-http-form-post-size: 2MB
# 是否支持 http2
http2.enabled: false
spring:
# Spring 网络配置
http:
encoding:
# 编码格式
charset: UTF-8
enabled: true
force: true
# servlet 配置
servlet:
# 文件上传配置
multipart:
# 是否支持文件上传
enabled: true
# 上传的文件的最大值
max-file-size: 100MB
# request 的最大值
max-request-size: 100MB
Spring boot 内嵌 Tomcat 的启动
Spring boot 对内嵌服务器的运用,会使用 ServletWebServerFactory :
// 接口
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
它的具体实现类有:
JettyServletWebServerFactory - eclipse jetty 内嵌服务对象的工厂类
TomcatServletWebServerFactory - apache tomcat 内嵌服务对象的工厂类
UndertowServletWebServerFactory - jboss undertow 内嵌服务对象的工厂类
本例中由于默认内嵌 Tomcat 服务包,所以 Spring boot 会使用 TomcatServletWebServerFactory:
// step 1
// TomcatServletWebServerFactory.class
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir
= (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
// Tomcat 配置 baseDir,如果不配置的话会创建一个临时目录,用来存放一些 log 文件等临时文件
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 创建 Connector,并存入 protocol
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
// 在 Tomcat 的 service 中存入 connector,由于是内嵌的 Tomcat,所以 service 只允许有一个
tomcat.getService().addConnector(connector);
// 根据配置对 connector 进行配置
customizeConnector(connector);
// 本质上这行代码和上述 tomcat.getService().addConnector(connector) 的功能是一样的,不太理解为什么又重新 set 了一遍
tomcat.setConnector(connector);
// 关闭 host 的自动扫描
tomcat.getHost().setAutoDeploy(false);
// 配置 engine
configureEngine(tomcat.getEngine());
// 添加一些默认的 connector,一般是空的
for (Connector additionalConnector
: this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// ServletContextInitializer 用于封装 servlet 的配置信息,并将 servlet 注册到 context 上
// 此处会创建一个 context 对象,并注册到 host 中
// 由于 spring mvc 内部实际上只有一个 dispatcher servlet,所以此处的数组一般只有一个
prepareContext(tomcat.getHost(), initializers);
// 用一个 TomcatWebServer 对象包装原生的 Tomcat 对象
return getTomcatWebServer(tomcat);
}
// step 2
// TomcatServletWebServerFactory.class
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
// step 3
// TomcatWebServer.class
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
// step 4
// TomcatWebServer.class
private void initialize() throws WebServerException {
// 记录日志
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
// 给 context 增加生命周期监听器
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
removeServiceConnectors();
}
});
// 此处启动 tomcat
this.tomcat.start();
// 此处检测一下是否启动成功,如果失败了就直接抛出错误
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(
context, context.getNamingToken(), getClass().getClassLoader());
} catch (NamingException ex) { }
// 启动一条主线程
// 由于 Tomcat 的所有现场都默认是守护线程,所以需要一条非守护线程来确保项目不退出
startDaemonAwaitThread();
} catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。