内嵌 Tomcat 配置
概述
Spring Boot 内嵌 Tomcat 是 Servlet Web 应用的默认运行容器。TomcatServletWebServerFactory 负责创建 TomcatWebServer,而 ServletWebServerFactoryAutoConfiguration 负责自动配置。
本文深入拆解内嵌 Tomcat 的完整配置和生命周期管理,涵盖从默认配置、连接器配置、优雅启停到 SSL、压缩等 12 个细节点。
本文基于 Spring Boot 3.x + Tomcat 10.x 源码分析。
1. 整体架构
TomcatServletWebServerFactory
│
├─ 创建 Tomcat 实例
│ └─ new Tomcat()
│
├─ 配置 Connector
│ ├─ port, address, protocol
│ ├─ compression, SSL
│ ├─ keepAlive, acceptCount
│ └─ maxSwallowSize, maxConnections
│
├─ 配置 Context
│ ├─ contextPath, docBase
│ ├─ errorPage, sessionTimeout
│ └─ servlet, filter, listener
│
├─ 配置 Engine
│ └─ engineName, host
│
├─ 执行 Customizer 链
│ ├─ WebServerFactoryCustomizer
│ ├─ TomcatConnectorCustomizer
│ ├─ TomcatContextCustomizer
│ └─ ErrorPageRegistrar
│
└─ getWebServer() → TomcatWebServer
└─ start()
├─ Tomcat.start()
├─ Connector.start()
├─ ProtocolHandler.start()
└─ await()2. TomcatServletWebServerFactory 的默认配置
2.1 源码
java
// TomcatServletWebServerFactory.java
public class TomcatServletWebServerFactory
extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory {
public TomcatServletWebServerFactory() {
// 默认端口: 8080
super();
}
// 父类 AbstractServletWebServerFactory 的默认值
// 由 @ConfigurationProperties(prefix = "server") 绑定
private int port = 8080; // server.port
private String contextPath = ""; // server.servlet.context-path
private String displayName = "application"; // 显示名称
private Session session = new Session(); // server.servlet.session
private Encoding encoding; // server.servlet.encoding
// Tomcat 特有默认配置
private Duration connectionTimeout = Duration.ofSeconds(20); // server.tomcat.connection-timeout
private int maxConnections = 8192; // server.tomcat.max-connections
private int acceptCount = 100; // server.tomcat.accept-count
private int maxKeepAliveRequests = 200; // server.tomcat.max-keep-alive-requests
private Duration keepAliveTimeout; // server.tomcat.keep-alive-timeout
private int maxSwallowSize = 2 * 1024 * 1024; // server.tomcat.max-swallow-size (2MB)
}2.2 默认配置一览
| 配置项 | 默认值 | 配置属性 |
|---|---|---|
| 端口 | 8080 | server.port |
| Context Path | "" (根路径) | server.servlet.context-path |
| 连接超时 | 20s | server.tomcat.connection-timeout |
| 最大连接数 | 8192 | server.tomcat.max-connections |
| Accept 队列长度 | 100 | server.tomcat.accept-count |
| 最大 Keep-Alive 请求数 | 200 | server.tomcat.max-keep-alive-requests |
| Keep-Alive 超时 | 不设置 | server.tomcat.keep-alive-timeout |
| 最大丢弃体大小 | 2MB | server.tomcat.max-swallow-size |
| URI 编码 | UTF-8 | server.servlet.encoding.charset |
| Session 超时 | 30m | server.servlet.session.timeout |
3. configureConnector() 的 Connector 配置
3.1 源码
java
// TomcatServletWebServerFactory.java
protected void configureConnector(Connector connector, boolean bindOnStartup) {
// 1. 设置连接超时
connector.setConnectionTimeout(
(int) this.connectionTimeout.toMillis());
// 2. 最大连接数
connector.setMaxPostSize(this.maxConnections);
connector.addConnectionListener(new ConnectionLimitListener(
this.maxConnections));
// 3. 端口绑定方式
if (bindOnStartup) {
connector.setProperty("bindOnStartup", "false");
}
// 4. 设置协议处理器
// server.tomcat.protocol 属性可以用来指定协议处理器
String protocol = this.protocol;
if (protocol != null) {
connector.setProperty("protocol", protocol);
}
// 5. 设置 Acceptor 线程数
connector.setProperty("acceptorThreadCount",
String.valueOf(this.acceptorThreadCount));
// 6. 设置最大线程数
connector.setProperty("maxThreads",
String.valueOf(this.maxThreads));
// 7. 设置最小空闲线程数
connector.setProperty("minSpareThreads",
String.valueOf(this.minSpareThreads));
}3.2 protocolHandlerClassName 的选择
java
// TomcatServletWebServerFactory.java
private String protocol = "org.apache.coyote.http11.Http11NioProtocol";
protected String getProtocolHandlerClassName() {
// 从 server.tomcat.protocol 属性读取
String protocol = this.protocol;
if (protocol != null && !protocol.contains(".")) {
// 简短名称 → 映射到完整类名
switch (protocol) {
case "HTTP/1.1":
return "org.apache.coyote.http11.Http11NioProtocol";
case "AJP/1.3":
return "org.apache.coyote.ajp.AjpNioProtocol";
default:
// 尝试当做完整类名使用
}
}
return protocol != null ? protocol
: "org.apache.coyote.http11.Http11NioProtocol";
}3.3 支持的协议
server.tomcat.protocol 值 | 协议处理器 | 说明 |
|---|---|---|
不设置 / HTTP/1.1 | Http11NioProtocol | 默认 NIO 连接器 |
AJP/1.3 | AjpNioProtocol | AJP 协议(与 Apache HTTP Server 配合) |
org.apache.coyote.http11.Http11AprProtocol | Http11AprProtocol | APR 原生连接器(需安装 APR 库) |
3.4 Connector 配置项的最终映射
java
// configureConnector() 中设置的属性最终通过 Tomcat 的 Connector.setProperty() 传递
connector.setProperty("maxThreads", "200"); → Http11NioProtocol.setMaxThreads(200)
connector.setProperty("acceptCount", "100"); → ServerSocket.accept() 的 backlog 参数
connector.setProperty("maxConnections", "8192"); → 最大并发连接数
connector.setProperty("connectionTimeout", "20000");→ 连接超时毫秒
connector.setProperty("compression", "on"); → 启用压缩
connector.setProperty("compressableMimeType", → 可压缩的 MIME 类型列表
"text/html,text/xml,text/plain,text/css,...");4. TomcatWebServer.start() 优雅启动流程
4.1 源码
java
// TomcatWebServer.java
public class TomcatWebServer implements WebServer {
private final Tomcat tomcat;
private final boolean autoStart;
@Override
public void start() throws WebServerException {
// 1. 检查是否已启动
if (this.started) {
return;
}
// 2. 初始化 Tomcat 引擎
initialize();
// 3. 标记启动状态
this.started = true;
}
private void initialize() throws WebServerException {
// 1. 添加延迟关闭钩子
addInstanceIdToEngineName();
// 2. 移除无用的 Connector 以进行重新配置
removeServiceConnectors();
// 3. 启动 Tomcat 服务器
this.tomcat.start();
// 4. 重新添加 Connector(上一步移除的)
addPreviouslyRemovedConnectors();
// 5. 启动额外 Connector
for (Connector additionalConnector : this.additionalConnectors) {
additionalConnector.start();
}
// 6. 等待所有 Connector 就绪
checkThatConnectorsHaveStarted();
// 7. 设置非阻塞关闭
setDaemon(false);
}
}4.2 Tomcat 内部的启动序列
Tomcat.start()
│
├─ 1. StandardServer.start()
│ └─ LifecycleEvent: BEFORE_START_EVENT
│
├─ 2. StandardService.start()
│ ├─ 启动 Executor(线程池)
│ ├─ 启动 Engine(Catalina Engine)
│ │ └─ StandardEngine.start()
│ │ └─ StandardHost.start()
│ │ └─ StandardContext.start()
│ │ ├─ 加载 Servlet
│ │ ├─ 执行 ServletContainerInitializer
│ │ ├─ 调用 Servlet.init()
│ │ └─ Context 就绪
│ │
│ ├─ 启动 Connector
│ │ └─ Connector.start()
│ │ └─ ProtocolHandler.start()
│ │ └─ Http11NioProtocol.start()
│ │ ├─ 创建 Acceptor 线程
│ │ ├─ 创建 Poller 线程
│ │ ├─ ServerSocketChannel.bind(port)
│ │ └─ 开始接受请求
│ │
│ └─ LifecycleEvent: AFTER_START_EVENT
│
└─ 3. 注册 JVM Shutdown Hook5. TomcatWebServer.stop() 优雅关闭流程
5.1 源码
java
// TomcatWebServer.java
@Override
public void stop() throws WebServerException {
// 1. 检查是否已停止
if (!this.started) {
return;
}
this.started = false;
// 2. 优雅关闭
if (this.gracefulShutdown != null) {
// 先执行优雅关闭(等待请求处理完成)
this.gracefulShutdown.shutDownGracefully();
}
// 3. 停止 Tomcat
try {
this.tomcat.stop(); // stop() → await() → destroy()
this.tomcat.destroy();
} catch (LifecycleException ex) {
throw new WebServerException("Unable to stop tomcat", ex);
}
}5.2 Tomcat 内部的停止序列
Tomcat.stop()
│
├─ 1. StandardServer.stop()
│ └─ LifecycleEvent: BEFORE_STOP_EVENT
│
├─ 2. StandardService.stop()
│ ├─ 停止 Connector
│ │ └─ Connector.stop()
│ │ └─ ProtocolHandler.stop()
│ │ └─ Http11NioProtocol.stop()
│ │ ├─ 关闭 Poller 线程
│ │ ├─ 关闭 Acceptor 线程
│ │ └─ 关闭 ServerSocketChannel
│ │
│ ├─ 停止 Engine
│ │ └─ StandardEngine.stop()
│ │ └─ StandardHost.stop()
│ │ └─ StandardContext.stop()
│ │ ├─ 调用 Servlet.destroy()
│ │ └─ 释放资源
│ │
│ └─ 停止 Executor(线程池)
│
├─ 3. await() —— 等待所有请求完成
│
└─ 4. destroy()5.3 关闭钩子的注册
java
// TomcatWebServer.java
private void addInstanceIdToEngineName() {
// 注册 JVM Shutdown Hook
// 当 JVM 收到 SIGTERM 信号时,触发 Tomcat 关闭
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
TomcatWebServer.this.stop();
} catch (Exception ex) {
// 忽略关闭时的异常
}
}));
}6. server.tomcat.accept-count 与 max-connections 实现
6.1 accept-count 的实现
java
// Connector.java (Tomcat)
public class Connector {
// acceptCount = ServerSocket 的 backlog 参数
// 当服务器正在处理请求时,新的连接请求会进入 backlog 队列
// 队列满后,操作系统会拒绝新连接
private int acceptCount = 100;
public void setProperty(String name, String value) {
if ("acceptCount".equals(name)) {
// 最终传递给 ServerSocket.bind(endpoint, backlog)
this.acceptCount = Integer.parseInt(value);
}
}
}java
// NioEndpoint.java (Tomcat)
public class NioEndpoint extends AbstractEndpoint {
@Override
public void bind() throws Exception {
// 创建 ServerSocketChannel 时设置 backlog
serverSock = ServerSocketChannel.open();
serverSock.socket().bind(
new InetSocketAddress(getPort()),
getAcceptCount()); // ← backlog = acceptCount
}
}6.2 max-connections 的实现
java
// TomcatServletWebServerFactory.java
private int maxConnections = 8192;
// 在配置 Connector 时设置
protected void configureConnector(Connector connector, boolean bindOnStartup) {
// maxConnections = 最大并发连接数
// 当连接数达到此值时,新的连接会被阻塞
connector.addConnectionListener(
new ConnectionLimitListener(this.maxConnections));
}
// ConnectionLimitListener.java —— Spring Boot 内部类
private static class ConnectionLimitListener
implements ConnectionListener {
private final int maxConnections;
private final AtomicInteger connectionCount = new AtomicInteger();
@Override
public void connectionEvent(Connector connector,
SocketEvent event, SocketWrapperBase<?> socket) {
if (event == SocketEvent.OPEN_READ) {
// 新连接建立
int count = connectionCount.incrementAndGet();
if (count > maxConnections) {
// 超过最大连接数 → 关闭连接
socket.close();
}
} else if (event == SocketEvent.CLOSE) {
// 连接关闭 → 计数减一
connectionCount.decrementAndGet();
}
}
}6.3 accept-count 与 max-connections 的关系
请求到达
│
├─ 并发连接数 < max-connections (8192)
│ └─ 正常接收 → 添加到 backlog 队列等待处理
│
├─ 并发连接数 >= max-connections (8192)
│ └─ 新连接被拒绝(ConnectionLimitListener 关闭)
│
├─ backlog 队列已满 (队列长度 = acceptCount = 100)
│ └─ 操作系统拒绝连接(TCP RST)
│
└─ backlog 队列未满
└─ 在队列中等待 worker 线程处理7. server.tomcat.max-keep-alive-requests 与 keep-alive-timeout
7.1 实现源码
java
// TomcatServletWebServerFactory.java
private int maxKeepAliveRequests = 200; // 默认 200
private Duration keepAliveTimeout; // 默认不设置
// 在 configureConnector() 中设置
protected void configureConnector(Connector connector, boolean bindOnStartup) {
// maxKeepAliveRequests → 设置 HTTP 最大 Keep-Alive 请求数
connector.setProperty("maxKeepAliveRequests",
String.valueOf(this.maxKeepAliveRequests));
// keepAliveTimeout → 设置 Keep-Alive 超时时间
if (this.keepAliveTimeout != null) {
connector.setProperty("keepAliveTimeout",
String.valueOf(this.keepAliveTimeout.toMillis()));
}
}7.2 Keep-Alive 的工作原理
HTTP Keep-Alive 生命周期
│
├─ 1. 客户端发送 GET / HTTP/1.1
│ Connection: keep-alive
│
├─ 2. 服务器处理并返回响应
│ Connection: keep-alive
│ Keep-Alive: timeout=60, max=200
│ ← 连接不关闭,复用 TCP 连接
│
├─ 3. 客户端在同一连接上发送第二个请求
│ 无需重新建立 TCP 连接(节省 3 次握手时间)
│
├─ 4. ... 持续复用
│
└─ 5. 超过 maxKeepAliveRequests (200)
或超过 keepAliveTimeout (60s) 无请求
→ 关闭 TCP 连接7.3 在 Tomcat 中的实现
java
// AbstractHttp11Protocol.java (Tomcat)
public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
private int maxKeepAliveRequests = 200;
private int keepAliveTimeout = -1; // 单位: 毫秒
public void setMaxKeepAliveRequests(int maxKeepAliveRequests) {
this.maxKeepAliveRequests = maxKeepAliveRequests;
}
public void setKeepAliveTimeout(int keepAliveTimeout) {
this.keepAliveTimeout = keepAliveTimeout;
}
}
// Http11Processor.java (Tomcat) 处理 Keep-Alive
public class Http11Processor extends AbstractHttp11Processor {
@Override
public SocketState service(SocketWrapperBase<?> socketWrapper)
throws IOException {
// 处理请求
// ...
// 检查是否应该关闭连接
if (keepAlive) {
// 自增请求计数
keepAliveRequestCount++;
// 达到最大请求数 → 关闭
if (keepAliveRequestCount >= maxKeepAliveRequests) {
return SocketState.CLOSED;
}
// 未超时 → 继续复用
return SocketState.OPEN;
} else {
return SocketState.CLOSED;
}
}
}8. server.tomcat.max-swallow-size 含义
8.1 作用
maxSwallowSize 不是请求体的大小限制,而是当请求被丢弃时,Tomcat 最多丢弃多少字节。当 Tomcat 决定拒绝一个请求(如超过 maxPostSize)时,它需要"吞掉"请求体中未读取的数据,以便连接可以继续复用。
8.2 源码
java
// TomcatServletWebServerFactory.java
private int maxSwallowSize = 2 * 1024 * 1024; // 2MB
// 默认丢弃最多 2MB
// 如果请求体超过 2MB,Tomcat 会在 discard 后直接关闭连接java
// CoyoteAdapter.java (Tomcat)
public class CoyoteAdapter implements Adapter {
@Override
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res) throws Exception {
// 处理请求
// ...
// 后处理:丢弃未读取的请求体
if (status == SocketState.LONG) {
// 丢弃剩余的请求体字节
// 最多丢弃 maxSwallowSize 字节
InputBuffer inputBuffer = req.getInputBuffer();
long swallowed = inputBuffer.swallow();
if (swallowed > maxSwallowSize) {
// 超过最大丢弃量 → 关闭连接
status = SocketState.CLOSED;
}
}
}
}8.3 与 maxPostSize 的区别
| 配置 | 默认值 | 作用 |
|---|---|---|
server.tomcat.max-swallow-size | 2MB | 请求被拒绝时,最多丢弃多少字节的请求体 |
server.servlet.multipart.max-file-size | 1MB | 上传文件的最大大小 |
server.servlet.multipart.max-request-size | 10MB | 整个 multipart 请求的最大大小 |
9. compression 压缩配置
9.1 源码
java
// TomcatServletWebServerFactory.java
private final Compression compression = new Compression();
public static class Compression {
// 是否启用压缩
private boolean enabled = false;
// 压缩的 MIME 类型
private String[] mimeTypes = new String[] {
"text/html", "text/xml", "text/plain",
"text/css", "text/javascript",
"application/javascript", "application/json",
"application/xml"
};
// 最小压缩大小(字节)
private int minResponseSize = 2048;
// getter / setter
}9.2 在 configureConnector() 中的应用
java
// TomcatServletWebServerFactory.java
private void customizeCompression(Connector connector) {
if (!this.compression.isEnabled()) {
return;
}
// 启用压缩
connector.setProperty("compression", "on");
// 设置可压缩的 MIME 类型
String mimeTypes = String.join(",",
this.compression.getMimeTypes());
if (!mimeTypes.isEmpty()) {
connector.setProperty("compressableMimeType", mimeTypes);
}
// 设置最小压缩大小
connector.setProperty("compressableMimeType", mimeTypes);
connector.setProperty("compressionMinSize",
String.valueOf(this.compression.getMinResponseSize()));
}9.3 配置示例
yaml
server:
tomcat:
compression:
enabled: true # 启用压缩
mime-types: # 可压缩的 MIME 类型
- text/html
- text/css
- application/json
min-response-size: 2048 # 响应体超过 2KB 才压缩9.4 Tomcat 内部的压缩实现
服务器响应
│
├─ 响应体大小 >= minResponseSize (2048)? ← 否 → 不压缩
│
├─ 响应类型在 compressableMimeType 列表中? ← 否 → 不压缩
│
├─ 客户端 Accept-Encoding 包含 gzip? ← 否 → 不压缩
│
└─ 所有条件满足
└─ CompressionFilter.setCompression("on")
└─ GzipOutputFilter
├─ 使用 GzipOutputStream 压缩响应体
└─ 设置 Content-Encoding: gzip10. SSL 配置 SslConnectorCustomizer
10.1 源码
java
// TomcatServletWebServerFactory.java
private Ssl ssl; // ← 由 @ConfigurationProperties 绑定
// SslConnectorCustomizer 负责将 SSL 配置应用到 Connector
class SslConnectorCustomizer {
void customize(Connector connector) {
// 1. 创建 SSLHostConfig
SSLHostConfig sslHostConfig = new SSLHostConfig();
// 2. 设置 SSL 协议
sslHostConfig.setProtocols(
String.join(",", this.ssl.getEnabledProtocols()));
// 3. 设置密码套件
sslHostConfig.setCiphers(
String.join(",", this.ssl.getCiphers()));
// 4. 创建 Certificate
SSLHostConfigCertificate certificate =
new SSLHostConfigCertificate(
sslHostConfig, SSLHostConfigCertificate.Type.UNDEFINED);
// 5. 从 SslStoreProvider 加载密钥库
SslStoreProvider sslStoreProvider = getSslStoreProvider();
if (sslStoreProvider != null) {
certificate.setKeystore(
sslStoreProvider.getKeyStore());
certificate.setKeystorePassword(
sslStoreProvider.getKeyStorePassword());
} else {
// 从文件加载密钥库
certificate.setKeystoreFile(this.ssl.getKeyStore());
certificate.setKeystorePassword(
this.ssl.getKeyStorePassword());
}
// 6. 将 Certificate 添加到 SSLHostConfig
sslHostConfig.addCertificate(certificate);
// 7. 将 SSLHostConfig 添加到 Connector
connector.addSslHostConfig(sslHostConfig);
}
}10.2 配置示例
yaml
server:
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: secret
key-store-type: PKCS12
key-alias: tomcat
enabled-protocols:
- TLSv1.2
- TLSv1.3
ciphers:
- TLS_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA38410.3 SSL 连接器的创建
java
// TomcatServletWebServerFactory.java
private Connector createSslConnector() {
// 创建支持 SSL 的 Connector
Connector connector = new Connector(getProtocolHandlerClassName());
connector.setPort(getPort());
// 设置 SSL 配置
SslConnectorCustomizer sslCustomizer = new SslConnectorCustomizer();
sslCustomizer.customize(connector);
return connector;
}11. GracefulShutdown 实现
11.1 源码
java
// TomcatWebServer.java
public class TomcatWebServer implements WebServer {
private volatile GracefulShutdown gracefulShutdown;
// 优雅关闭的入口
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
if (this.gracefulShutdown == null) {
// 不支持的服务器类型
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
return;
}
// 委托给 GracefulShutdown
this.gracefulShutdown.shutDownGracefully(callback);
}
}
// GracefulShutdown.java
final class GracefulShutdown {
private final Tomcat tomcat;
private final Duration gracePeriod; // server.shutdown.grace-period
// 优雅关闭核心方法
void shutDownGracefully(GracefulShutdownCallback callback) {
// 1. 关闭所有 Connector(停止接受新请求)
for (Connector connector : this.tomcat.getService()
.findConnectors()) {
connector.pause(); // 暂停连接器(继续处理已有请求)
}
// 2. 等待正在处理的请求完成(最多等待 gracePeriod)
long endTime = System.currentTimeMillis()
+ this.gracePeriod.toMillis();
while (System.currentTimeMillis() < endTime) {
// 检查是否有活跃请求
if (getActiveRequestCount() == 0) {
// 所有请求已完成
callback.shutdownComplete(
GracefulShutdownResult.IDLE);
return;
}
// 休眠一小段时间再检查
Thread.sleep(100);
}
// 3. 超时 → 强制关闭
callback.shutdownComplete(
GracefulShutdownResult.TIMEOUT);
}
// 获取当前活跃请求数
private int getActiveRequestCount() {
int count = 0;
for (Connector connector : this.tomcat.getService()
.findConnectors()) {
// 从 ProtocolHandler 获取活跃请求数
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
count += ((AbstractProtocol<?>) handler)
.getActiveRequestCount();
}
}
return count;
}
}11.2 配置示例
yaml
server:
shutdown: graceful # 启用优雅关闭
tomcat:
graceful:
shutdown:
grace-period: 30s # 最多等待 30 秒11.3 优雅关闭流程
应用收到 SIGTERM(或调用 ApplicationContext.close())
│
├─ 1. TomcatWebServer.shutDownGracefully()
│ └─ Connector.pause() ← 停止接受新请求
│
├─ 2. 等待活跃请求完成
│ ├─ 每 100ms 检查一次活跃请求数
│ ├─ 活跃请求数为 0 → IDLE 完成
│ └─ 超过 grace-period (30s) → TIMEOUT 强制关闭
│
├─ 3. Tomcat.stop()
│ ├─ Connector.stop() ← 释放端口
│ ├─ Engine.stop() ← 销毁 Servlet
│ └─ Executor.shutdown() ← 关闭线程池
│
└─ 4. ApplicationContext 关闭完成12. TomcatServletWebServerFactory.customize()
12.1 源码
java
// TomcatServletWebServerFactory.java (通过 ConfigurableTomcatWebServerFactory 接口)
public void customize(TomcatServletWebServerFactory factory) {
// 内部执行 Customizer 链
}
// 在 getWebServer() 中调用
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 执行 Customizer 链
customize();
// 创建 Tomcat 实例
Tomcat tomcat = new Tomcat();
// ... 创建并配置 Tomcat
return new TomcatWebServer(tomcat);
}
// 执行 Customizer 链
private void customize() {
// 1. WebServerFactoryCustomizer(通用)
for (WebServerFactoryCustomizer<TomcatServletWebServerFactory> customizer :
this.webServerFactoryCustomizers) {
customizer.customize(this);
}
// 2. TomcatConnectorCustomizer(连接器定制)
for (TomcatConnectorCustomizer customizer :
this.tomcatConnectorCustomizers) {
customizer.customize(getConnector());
}
// 3. TomcatContextCustomizer(上下文定制)
for (TomcatContextCustomizer customizer :
this.tomcatContextCustomizers) {
customizer.customize(getContext());
}
// 4. ErrorPageRegistrar(错误页注册)
for (ErrorPageRegistrar registrar : this.errorPageRegistrars) {
registrar.registerErrorPages(getErrorPages());
}
}12.2 四种 Customizer
| Customizer | 定制目标 | 使用示例 |
|---|---|---|
WebServerFactoryCustomizer<TomcatServletWebServerFactory> | 整个 Factory | 修改端口、上下文路径等 |
TomcatConnectorCustomizer | Connector 实例 | 添加额外的 Connector 属性 |
TomcatContextCustomizer | Context 实例 | 添加 Tomcat Valve、监听器 |
ErrorPageRegistrar | 错误页注册表 | 注册自定义错误页 |
12.3 自定义示例
java
@Component
public class MyTomcatCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
// 修改端口
factory.setPort(9090);
// 配置连接器
factory.addConnectorCustomizers(connector -> {
connector.setProperty("maxThreads", "500");
connector.setProperty("connectionTimeout", "30000");
});
// 配置上下文
factory.addContextCustomizers(context -> {
context.addWelcomeFile("index.html");
});
// 注册错误页
factory.addErrorPages(new ErrorPage(
HttpStatus.NOT_FOUND, "/error/404.html"));
}
}13. 端口随机分配 0
13.1 工作原理
yaml
server:
port: 0 # ← 随机端口13.2 源码
java
// TomcatServletWebServerFactory.java
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 1. 创建 Tomcat
Tomcat tomcat = new Tomcat();
// 2. 设置端口
tomcat.setPort(this.port); // port = 0
// 3. 创建 Connector
Connector connector = new Connector(getProtocolHandlerClassName());
connector.setPort(this.port); // port = 0
// ... 配置 Connector
// 4. 启动 Tomcat
tomcat.start();
// 5. 获取实际分配的端口
int actualPort = tomcat.getConnector().getLocalPort();
// actualPort = 操作系统分配的随机端口(如 32845)
return new TomcatWebServer(tomcat, actualPort);
}13.3 获取随机端口
java
// 在应用中获取实际分配的端口
@Component
public class PortListener implements ApplicationListener<WebServerInitializedEvent> {
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
// 获取实际端口
int port = event.getWebServer().getPort();
System.out.println("应用运行在端口: " + port);
}
}java
// 或者通过 @LocalServerPort 注入(Spring Boot Test)
@SpringBootTest(properties = "server.port=0")
class MyTest {
@LocalServerPort
private int port; // 注入实际端口
@Test
void test() {
// 使用 port 变量
System.out.println("测试端口: " + port);
}
}13.4 端口 0 在操作系统层面的原理
Java ServerSocket.bind(port=0)
│
└─ OS 内核:
├─ 从临时端口范围(Linux: 32768-60999, Windows: 49152-65535)中
├─ 选择一个当前未被使用的端口
└─ 分配给当前进程
→ ServerSocket.getLocalPort() 获取实际端口号总结
| # | 细节点 | 核心要点 |
|---|---|---|
| ① | 默认配置 | 8080 端口、/ context path、UTF-8 编码、20s 连接超时 |
| ② | configureConnector() | 设置超时、最大连接数、协议处理器、线程池参数到 Connector |
| ③ | start() 优雅启动 | Tomcat.start() → Connector.start() → ProtocolHandler.start() → ServerSocketChannel.bind() |
| ④ | stop() 优雅关闭 | GracefulShutdown 暂停连接器 → 等待活跃请求 → Tomcat.stop() → Servlet.destroy() |
| ⑤ | accept-count / max-connections | acceptCount = ServerSocket.bind(backlog),maxConnections = ConnectionLimitListener 计数 |
| ⑥ | Keep-Alive | maxKeepAliveRequests(默认 200) 超过后关闭连接,keepAliveTimeout 空闲超时 |
| ⑦ | maxSwallowSize | 丢弃未读取请求体的最大字节数(2MB),超过则关闭连接 |
| ⑧ | compression 压缩 | CompressionConfig → compression=on + compressableMimeType + minResponseSize |
| ⑨ | SSL 配置 | SslConnectorCustomizer 创建 SSLHostConfig + Certificate,加载密钥库 |
| ⑩ | GracefulShutdown | Connector.pause() → 等待活跃请求完成(最多 gracePeriod) → 超时强制关闭 |
| ⑪ | customize() 链 | 4 种 Customizer 链:WebServerFactoryCustomizer → TomcatConnectorCustomizer → TomcatContextCustomizer → ErrorPageRegistrar |
| ⑫ | 端口 0 | OS 随机分配临时端口,通过 WebServerInitializedEvent 或 @LocalServerPort 获取实际端口 |