一、核心概念
1.1 什么是内嵌 Tomcat?
- 内嵌 (Embedded):指 Tomcat 服务器的运行实例被打包在 Spring Boot 应用的 JAR/WAR 文件内部,与应用代码一起启动和停止。
- 无需外部安装:开发者无需在服务器上单独安装和配置 Tomcat。
- 简化部署:应用通过
java -jar your-app.jar
即可启动,包含 Web 服务器和应用逻辑。
1.2 Spring Boot 如何集成 Tomcat?
- 依赖驱动:当项目引入
spring-boot-starter-web
依赖时,它会自动引入spring-boot-starter-tomcat
。 - 自动配置:
spring-boot-autoconfigure
模块中的ServletWebServerFactoryAutoConfiguration
类会检测到 Tomcat 相关的类在 classpath 中,自动配置一个TomcatServletWebServerFactory
。 - 启动:
SpringApplication
在启动时,通过ServletWebServerFactory
创建并启动内嵌的 Tomcat 实例。
1.3 关键组件
组件 | 说明 |
---|---|
TomcatServletWebServerFactory |
核心工厂类,负责创建和配置 Tomcat 实例。 |
Tomcat |
Apache Tomcat 的 Java 对象实例。 |
Connector |
Tomcat 的连接器,负责处理网络请求(HTTP/HTTPS)。一个 Tomcat 可以有多个 Connector。 |
ServletWebServerApplicationContext |
Spring 应用上下文,负责启动和管理内嵌 Web 服务器。 |
EmbeddedWebApplicationContext |
老版本上下文(已过时),新版本为 ServletWebServerApplicationContext 。 |
1.4 与传统部署 (WAR + 外部 Tomcat) 的对比
特性 | 内嵌 Tomcat (JAR) | 外部 Tomcat (WAR) |
---|---|---|
部署方式 | java -jar app.jar |
将 WAR 包部署到已安装的 Tomcat webapps 目录 |
运维复杂度 | 低 | 高(需管理 Tomcat 服务) |
资源占用 | 每个应用独立进程 | 多个应用共享同一个 Tomcat 进程 |
隔离性 | 高(进程级隔离) | 低(应用级隔离,可能相互影响) |
启动速度 | 快 | 相对较慢 |
配置灵活性 | 高(可通过代码和配置文件) | 依赖外部 Tomcat 配置 (server.xml ) |
适用场景 | 微服务、云原生应用 | 传统单体应用、已有 Tomcat 环境 |
结论:现代 Spring Boot 应用推荐使用内嵌 Tomcat + JAR 部署。
二、详细操作步骤
步骤 1:创建 Spring Boot 项目(确保包含 Web 依赖)
使用 Spring Initializr:
- Group:
com.example
- Artifact:
demo-web
- Dependencies:
Spring Web
(spring-boot-starter-web
) - Packaging:
Jar
(默认)
- Group:
检查
pom.xml
:<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring-boot-starter-web 会 transitively 引入 spring-boot-starter-tomcat --> </dependencies>
主启动类:
// DemoWebApplication.java @SpringBootApplication public class DemoWebApplication { public static void main(String[] args) { SpringApplication.run(DemoWebApplication.class, args); } }
步骤 2:基础配置(通过 application.yml
)
这是最常用的方式,通过配置文件修改 Tomcat 的基本属性。
# application.yml
server:
port: 8080 # Tomcat 监听的端口
servlet:
context-path: /api # 应用上下文路径,访问时需要加上 /api
tomcat:
# 连接器 (Connector) 配置
uri-encoding: UTF-8 # URI 编码
max-threads: 200 # 最大工作线程数 (默认 200)
min-spare-threads: 10 # 最小空闲线程数 (默认 10)
max-connections: 8192 # 最大连接数 (默认 8192)
accept-count: 100 # 等待队列长度 (当所有线程忙时,新连接进入队列)
# 启用 HTTP/2 (需要 HTTPS)
# http2:
# enabled: true
# 访问日志
accesslog:
enabled: true # 启用访问日志
pattern: common # 日志格式: common, combined, 或自定义
# pattern: '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %Dms'
# %Dms 表示请求处理时间 (毫秒)
directory: /var/log/tomcat # 日志目录
file-date-format: .yyyy-MM-dd # 按天分割日志文件
# 远程 IP 防火墙 (可选)
# remoteip:
# remote-ip-header: x-forwarded-for
# protocol-header: x-forwarded-proto
步骤 3:配置 HTTPS
生成或获取 SSL 证书:
# 生成自签名证书 (仅用于测试) keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 -storetype PKCS12 \ -keystore keystore.p12 -validity 3650 -storepass changeit -keypass changeit # 将 keystore.p12 放在 src/main/resources 目录下
配置
application.yml
:server: port: 8443 ssl: enabled: true key-store: classpath:keystore.p12 # 证书路径 key-store-password: changeit # 证书密码 key-store-type: PKCS12 # 证书类型 key-alias: tomcat # 证书别名 # client-auth: need # 双向认证 (可选) # 如果需要同时支持 HTTP 和 HTTPS,配置重定向 # tomcat: # redirect-contextual-error-page: true
访问:
https://localhost:8443/api/...
步骤 4:通过 Java 代码配置(高级)
当配置文件无法满足需求时,可以创建 WebServerFactoryCustomizer
Bean。
// TomcatConfig.java
@Configuration
public class TomcatConfig {
@Value("${server.max-http-header-size:8192}")
private int maxHttpHeaderSize;
/**
* 配置 Tomcat 连接器 (Connector)
*/
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatConnectorCustomizer() {
return factory -> factory.addConnectorCustomizers(connector -> {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
// 设置最大 HTTP Header 大小 (字节)
protocol.setMaxHttpHeaderSize(maxHttpHeaderSize);
// 设置连接超时 (毫秒)
connector.setConnectionTimeout(20000);
// 设置最大 POST 大小 (字节), -1 表示无限制
connector.setMaxPostSize(2097152); // 2MB
// 设置 URI 编码
connector.setURIEncoding("UTF-8");
// 启用压缩 (可选)
// connector.setProperty("compression", "on");
// connector.setProperty("compressionMinSize", "2048");
// connector.setProperty("compressableMimeType", "text/html,text/xml,text/plain,text/css,text/javascript,application/javascript");
});
}
/**
* 配置 Tomcat 本身 (Engine, Host, Context)
*/
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
return factory -> {
factory.addContextCustomizers(context -> {
// 禁用会话持久化 (对于无状态应用)
context.setManager(null);
// 设置会话超时 (分钟)
context.setSessionTimeout(30);
// 添加 Valve (如访问日志 Valve,但通常用配置文件更好)
// AccessLogValve accessLogValve = new AccessLogValve();
// accessLogValve.setDirectory("/var/log/tomcat");
// accessLogValve.setPrefix("access_log");
// accessLogValve.setSuffix(".log");
// accessLogValve.setPattern("common");
// context.getPipeline().addValve(accessLogValve);
});
};
}
/**
* 配置额外的 Connector (例如同时监听 HTTP 8080 和 HTTPS 8443)
*/
@Bean
@ConditionalOnProperty(name = "server.ssl.enabled", havingValue = "true")
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> additionalConnectorCustomizer() {
return factory -> factory.addAdditionalTomcatConnectors(createStandardConnector());
}
private Connector createStandardConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(8080); // HTTP 端口
connector.setSecure(false);
// 可以配置重定向到 HTTPS
// connector.setRedirectPort(8443);
return connector;
}
}
步骤 5:配置文件上传大小限制
# application.yml
spring:
servlet:
multipart:
enabled: true # 启用文件上传
max-file-size: 10MB # 单个文件最大大小
max-request-size: 50MB # 整个 HTTP 请求最大大小 (包含多个文件)
location: /tmp # 上传文件的临时目录
步骤 6:启用 GZIP 压缩
# application.yml
server:
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
min-response-size: 1024 # 最小响应体大小 (字节),超过才压缩
步骤 7:打包和运行
打包:
mvn clean package # 生成 target/demo-web.jar
运行:
java -jar demo-web.jar # 或指定配置文件 java -jar demo-web.jar --spring.profiles.active=prod
验证:
- 访问
http://localhost:8080/api/hello
(假设有一个/hello
接口)。 - 查看日志,确认 Tomcat 启动成功。
- 访问
三、常见错误与解决方案
错误现象 | 原因分析 | 解决方案 |
---|---|---|
端口被占用 | 1. server.port 指定的端口已被其他进程使用2. 应用未完全退出,端口未释放 |
1. 更改 server.port 2. netstat -ano | findstr :8080 (Windows) / lsof -i :8080 (Mac/Linux) 找到进程并杀死 (kill -9 <pid> ) |
启动失败,提示找不到 Tomcat 类 | 1. 项目是 war 打包,但依赖了 spring-boot-starter-tomcat 且作用域为 provided 2. 依赖冲突 |
1. 如果是 WAR 部署到外部 Tomcat,确保 spring-boot-starter-tomcat 的 scope 为 provided 2. 检查 mvn dependency:tree ,排除冲突依赖 |
HTTP 413 (Payload Too Large) | 上传文件超过限制 | 1. 检查 spring.servlet.multipart.max-file-size 和 max-request-size 2. 检查 Tomcat maxPostSize (可通过代码配置) |
HTTP 400 (Bad Request) - Header too large | HTTP Header 过大 | 通过 WebServerFactoryCustomizer 增加 maxHttpHeaderSize |
HTTPS 无法访问 | 1. 证书配置错误 (key-store , password )2. 浏览器不信任自签名证书 |
1. 检查 application.yml 中的 SSL 配置2. 导入证书到浏览器信任列表,或使用可信 CA 签发的证书 |
访问日志不生成 | 1. server.tomcat.accesslog.enabled 未设置为 true 2. 日志目录无写入权限 |
1. 确认配置已启用 2. 检查 /var/log/tomcat 目录权限 (chmod 755 /var/log/tomcat ) |
四、注意事项
- JAR vs WAR:
- JAR: 使用内嵌 Tomcat,
spring-boot-starter-tomcat
为默认依赖。 - WAR: 部署到外部 Tomcat,需将
spring-boot-starter-tomcat
的 scope 设为provided
,并让主类继承SpringBootServletInitializer
。
- JAR: 使用内嵌 Tomcat,
- 配置优先级:
- 命令行参数 >
application.yml
>@Configuration
类中的@Bean
> 默认值。
- 命令行参数 >
- 线程模型:
- 默认使用
NIO
(非阻塞 I/O),性能优于BIO
。 max-threads
不宜设置过大,避免线程上下文切换开销。
- 默认使用
- 安全:
- 生产环境 HTTPS 是必须的。
- 避免使用默认的
server.servlet.session.cookie.name
(如JSESSIONID
),可自定义。
- 监控:
- 结合 Spring Boot Actuator (
/actuator/metrics/tomcat.*
) 监控 Tomcat 状态(线程数、请求量、错误率)。
- 结合 Spring Boot Actuator (
server.tomcat.basedir
:- 指定 Tomcat 工作目录(用于存放临时文件、日志等),默认为系统临时目录。建议显式设置。
五、使用技巧
5.1 动态修改端口
# 启动时指定
java -jar app.jar --server.port=9090
# 或通过环境变量
export SERVER_PORT=9090
java -jar app.jar
5.2 配置多个 Connector
如上文代码所示,通过 addAdditionalTomcatConnectors
添加 HTTP 和 HTTPS 连接器。
5.3 自定义 ErrorPage
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> errorPageCustomizer() {
return factory -> factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"));
}
5.4 禁用内嵌 Tomcat(使用 Undertow 或 Jetty)
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 使用 Undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!-- 或使用 Jetty -->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency> -->
</dependencies>
5.5 获取 Tomcat 实例信息
@Component
public class TomcatInfoPrinter implements ApplicationListener<WebServerInitializedEvent> {
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
if (event.getWebServer() instanceof TomcatWebServer) {
TomcatWebServer tomcatWebServer = (TomcatWebServer) event.getWebServer();
Tomcat tomcat = tomcatWebServer.getTomcat();
Connector connector = tomcat.getService().getConnectors()[0];
System.out.println("Tomcat Port: " + connector.getLocalPort());
System.out.println("Tomcat Server: " + tomcat.getServer());
}
}
}
六、最佳实践
- 使用 JAR 部署:简化部署和运维。
- HTTPS 强制:生产环境必须使用 HTTPS。
- 合理配置线程:根据应用负载和服务器资源设置
max-threads
和accept-count
。 - 启用访问日志:用于审计、分析流量和排查问题。
- 配置文件上传限制:防止恶意大文件上传。
- 启用 GZIP 压缩:减少网络传输量。
- 监控与告警:集成 Actuator + Prometheus + Grafana 监控 Tomcat 指标。
- 安全加固:隐藏服务器版本信息(通过
server.tomcat.redirect-contextual-error-page
或自定义错误页)。 - 优雅停机:配置
server.shutdown=graceful
(Spring Boot 2.3+),让 Tomcat 在收到关闭信号后等待正在处理的请求完成。 - 使用配置中心:将
application.yml
中的敏感配置(如端口、证书密码)放到 Nacos/Config Server。
七、性能优化
- 线程池调优:
max-threads
: 根据 CPU 核心数和应用 I/O 特性设置。通常 200-800。min-spare-threads
: 保持一定数量的空闲线程,避免频繁创建销毁。accept-count
: 队列过长可能导致请求堆积,过短可能导致连接拒绝。根据负载调整。
- 连接超时:
connection-timeout
: 避免连接长时间占用线程。设置为 20-30 秒。
- 禁用不必要的功能:
- 如果应用无状态,
context.setManager(null)
禁用会话管理。 - 禁用 JMX 如果不需要监控。
- 如果应用无状态,
- GZIP 压缩:
- 对文本类响应(HTML, CSS, JS, JSON)启用压缩,显著减少传输时间。
- JVM 调优:
- 合理设置堆内存 (
-Xms
,-Xmx
)。 - 选择合适的 GC 算法(如 G1GC)。
- 合理设置堆内存 (
- 操作系统优化:
- 增加文件描述符限制 (
ulimit -n
)。 - 调整 TCP 参数(如
tcp_tw_reuse
)。
- 增加文件描述符限制 (
- 使用 NIO2 (APR/native):
- Tomcat APR 库(基于 JNI)在高并发下性能优于 NIO。但需要安装 native 库,配置复杂,通常 NIO 足够。
总结
Spring Boot 内嵌 Tomcat 极大地简化了 Web 应用的开发和部署。
核心要点:
- 依赖驱动:
spring-boot-starter-web
自动引入 Tomcat。 - 配置方式:优先使用
application.yml
,复杂需求用WebServerFactoryCustomizer
。 - 关键配置:端口、线程、HTTPS、访问日志、文件上传、压缩。
- 生产实践:HTTPS、监控、日志、安全、性能调优。
通过本指南,你已掌握 Spring Boot 内嵌 Tomcat 的核心配置方法。记住,JAR 部署 + 配置文件 + Actuator 监控是现代 Spring Boot 应用的标准实践。