Spring Boot 定制嵌入式 Servlet 容器详解:配置修改与容器替换实战
Spring Boot 内置了 Tomcat(默认)、Jetty、Undertow 三种主流 Servlet 容器,无需外置服务器即可通过 java -jar
直接启动应用。实际开发中,常需根据需求 “修改容器配置”(如端口、编码)或 “替换容器”(如用 Jetty 替代 Tomcat)。从 “容器配置修改(两种方式)→ 容器替换(Tomcat 转 Jetty/Undertow)→ 底层原理” 三个维度,系统讲解嵌入式 Servlet 容器的定制方法,帮你灵活掌控应用部署环境。
嵌入式 Servlet 容器简介
Spring Boot 嵌入式容器的核心价值是 “简化部署”—— 无需手动安装、配置外置服务器(如 Tomcat),应用打包为 JAR 包后可独立运行。三种内置容器的特点对比如下:
容器名称 | 核心特点 | 适用场景 | 默认依赖 artifactId |
---|---|---|---|
Tomcat | 功能全面、兼容性好、社区活跃 | 绝大多数 Web 应用(默认选择) | spring-boot-starter-tomcat |
Jetty | 轻量级、启动快、内存占用低 | 轻量级应用、微服务(如 Spring Cloud 服务) | spring-boot-starter-jetty |
Undertow | 高性能、异步非阻塞、支持 HTTP/2 | 高并发场景(如秒杀、API 网关) | spring-boot-starter-undertow |
默认情况下,引入 spring-boot-starter-web
会自动依赖 Tomcat 容器,无需额外配置。
修改嵌入式 Servlet 容器配置
Spring Boot 提供两种方式修改容器配置(如端口、编码、连接超时),分别适用于 “简单配置” 和 “复杂定制” 场景。
方式一:配置文件修改(推荐,简单场景)
通过 application.properties
或 application.yml
直接配置 server
前缀的属性,Spring Boot 会自动将配置映射到容器(底层通过 ServerProperties
类实现)。
1. 核心配置属性(常用)
配置项 | 作用描述 | 示例值 | 适用容器 |
---|---|---|---|
server.port |
应用端口(0 表示随机端口) | 8081 、0 |
所有 |
server.servlet.context-path |
应用上下文路径(默认 / ) |
/demo |
所有 |
server.tomcat.uri-encoding |
Tomcat 编码格式(避免中文乱码) | UTF-8 |
Tomcat |
server.tomcat.max-threads |
Tomcat 最大线程数(控制并发能力) | 200 |
Tomcat |
server.tomcat.connection-timeout |
Tomcat 连接超时时间(毫秒) | 20000 (20 秒) |
Tomcat |
server.jetty.max-http-post-size |
Jetty 最大 POST 请求大小 | 10MB |
Jetty |
server.undertow.io-threads |
Undertow IO 线程数(处理连接) | 4 (默认 CPU 核心数) |
Undertow |
2. 配置示例(YML 格式)
1 | # 通用容器配置(所有容器生效) |
3. 底层原理:ServerProperties 类
配置文件中的 server
前缀属性之所以能生效,是因为 Spring Boot 提供 ServerProperties
类,它实现了 EmbeddedServletContainerCustomizer
接口,负责将配置映射到容器:
1 | // ServerProperties 类核心源码 |
方式二:自定义 EmbeddedServletContainerCustomizer(复杂场景)
当配置文件无法满足需求(如动态设置端口、自定义错误页面)时,可通过编写 EmbeddedServletContainerCustomizer
类型的 Bean,手动定制容器配置(该接口是 Spring Boot 提供的 “容器定制器”)。
1. 核心用法:注册定制器 Bean
通过 @Bean
注解注册 EmbeddedServletContainerCustomizer
,在 customize
方法中编写定制逻辑:
1 | import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; |
2. 关键能力:容器类型判断与特殊定制
不同容器的 API 不同,可通过 instanceof
判断容器类型,实现专属定制:
- Tomcat:强转为
TomcatEmbeddedServletContainerFactory
,定制线程数、错误页面、连接器; - Jetty:强转为
JettyEmbeddedServletContainerFactory
,定制线程池、连接器; - Undertow:强转为
UndertowEmbeddedServletContainerFactory
,定制 IO 线程、工作线程。
3. 示例:定制 Jetty 容器(最大 POST 大小)
1 |
|
替换嵌入式 Servlet 容器(Tomcat 转 Jetty/Undertow)
默认容器是 Tomcat,若需替换为 Jetty 或 Undertow,只需通过 Maven/Gradle 调整依赖(排除 Tomcat,引入目标容器),Spring Boot 会自动识别并使用新容器。
场景 1:替换为 Jetty 容器
Jetty 轻量级、启动快,适合微服务或轻量级应用。
1. Maven 依赖配置(排除 Tomcat,引入 Jetty)
1 | <!-- 引入 Web Starter(默认依赖 Tomcat,需排除) --> |
2. 验证替换结果
启动应用后,控制台会输出 Jetty 启动日志,证明容器已替换:
1 | 2024-05-20 10:00:00.000 INFO 1234 --- [ main] o.s.b.web.embedded.jetty.JettyWebServer : Jetty started on port(s) 8080 (http/1.1) with context path '/' |
场景 2:替换为 Undertow 容器
Undertow 基于异步非阻塞架构,性能优异,适合高并发场景(如秒杀、API 网关)。
1. Maven 依赖配置(排除 Tomcat,引入 Undertow)
1 | <!-- 引入 Web Starter,排除 Tomcat --> |
2. Undertow 专属配置(优化性能)
在配置文件中添加 Undertow 性能优化配置:
1 | server: |
3. 验证替换结果
启动应用后,控制台输出 Undertow 启动日志:
1 | 2024-05-20 10:05:00.000 INFO 5678 --- [ main] o.s.b.web.embedded.undertow.UndertowWebServer : Undertow started on port(s) 8080 (http) with context path '/' |
底层原理:Spring Boot 如何选择嵌入式容器?
Spring Boot 自动识别容器的核心是 “依赖检测”—— 通过 @ConditionalOnClass
注解判断类路径中是否存在目标容器的核心类,进而加载对应容器的自动配置。
以 Tomcat 为例,TomcatAutoConfiguration
类的条件注解如下:
1 | // Tomcat 自动配置类 |
同理,Jetty 和 Undertow 也有对应的自动配置类(JettyAutoConfiguration
、UndertowAutoConfiguration
),Spring Boot 会根据类路径中的依赖自动选择加载哪个配置类,从而确定使用的容器。
常见问题与解决方案
1. 容器替换后启动失败(类找不到)
问题原因:
- 排除 Tomcat 时依赖排除不完整(如遗漏
tomcat-embed-core
); - 目标容器依赖版本与 Spring Boot 版本不兼容。
解决方案:
- 确保通过
spring-boot-starter-jetty
/undertow
引入容器(而非直接引入第三方 Jetty/Undertow 依赖),Spring Boot 会自动管理版本; - 检查依赖排除是否完整(仅排除
spring-boot-starter-tomcat
即可,无需手动排除tomcat-embed-core
)。
2. 配置文件中的容器属性不生效
问题原因:
- 配置属性与容器不匹配(如 Tomcat 的
server.tomcat.max-threads
配置到 Jetty 容器); - 自定义
EmbeddedServletContainerCustomizer
覆盖了配置文件的属性(代码配置优先级高于配置文件)。
解决方案:
- 确认配置属性与当前容器匹配(参考本文 “核心配置属性” 表);
- 若需保留配置文件优先级,在
EmbeddedServletContainerCustomizer
中先读取配置文件属性,再进行定制。
3. Tomcat 启动报 “地址已在使用”(端口冲突)
问题原因:
- 配置的
server.port
已被其他进程占用; - 应用多次启动,旧进程未关闭。
解决方案:
- 更换端口(如
server.port=8081
); - 配置随机端口(
server.port=0
,Spring Boot 会自动分配未占用的端口); - 关闭占用端口的进程(Windows:
netstat -ano | findstr "8080"
查找进程 ID,再通过任务管理器关闭)。
总结
Spring Boot 嵌入式 Servlet 容器的定制核心是 “按需选择配置方式”:
- 简单配置:优先使用配置文件(
server
前缀属性),开发效率高,覆盖 80% 场景; - 复杂定制:通过
EmbeddedServletContainerCustomizer
编写代码,支持动态配置、容器专属定制; - 容器替换:调整 Maven 依赖(排除 Tomcat,引入 Jetty/Undertow),Spring Boot 自动识别并切换
v1.3.10