0%

springboot定制嵌入式的servlet容器

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.propertiesapplication.yml 直接配置 server 前缀的属性,Spring Boot 会自动将配置映射到容器(底层通过 ServerProperties 类实现)。

1. 核心配置属性(常用)
配置项 作用描述 示例值 适用容器
server.port 应用端口(0 表示随机端口) 80810 所有
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
2
3
4
5
6
7
8
9
10
11
12
13
# 通用容器配置(所有容器生效)
server:
port: 8081 # 应用端口
servlet:
context-path: /demo # 上下文路径(访问地址变为 http://localhost:8081/demo)
connection-timeout: 20000 # 连接超时(20 秒)

# Tomcat 专属配置(仅 Tomcat 生效)
tomcat:
uri-encoding: UTF-8 # 编码格式
max-threads: 200 # 最大线程数(并发能力)
min-spare-threads: 20 # 最小空闲线程数
basedir: ./tomcat-temp # Tomcat 临时文件目录(避免内存溢出)
3. 底层原理:ServerProperties 类

配置文件中的 server 前缀属性之所以能生效,是因为 Spring Boot 提供 ServerProperties 类,它实现了 EmbeddedServletContainerCustomizer 接口,负责将配置映射到容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ServerProperties 类核心源码
@ConfigurationProperties(prefix = "server") // 绑定配置文件中 "server" 前缀的属性
public class ServerProperties implements EmbeddedServletContainerCustomizer {
private int port = 8080; // 默认端口 8080
private String contextPath = ""; // 默认上下文路径

// 实现 EmbeddedServletContainerCustomizer 接口的方法,将配置应用到容器
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
// 端口配置
if (this.port != 0) {
container.setPort(this.port);
}
// 上下文路径配置
if (StringUtils.hasText(this.contextPath)) {
container.setContextPath(this.contextPath);
}
// 其他配置(编码、超时等)...
}
}

方式二:自定义 EmbeddedServletContainerCustomizer(复杂场景)

当配置文件无法满足需求(如动态设置端口、自定义错误页面)时,可通过编写 EmbeddedServletContainerCustomizer 类型的 Bean,手动定制容器配置(该接口是 Spring Boot 提供的 “容器定制器”)。

1. 核心用法:注册定制器 Bean

通过 @Bean 注解注册 EmbeddedServletContainerCustomizer,在 customize 方法中编写定制逻辑:

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
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ContainerConfig {

// 注册嵌入式 Servlet 容器定制器
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
// 返回定制器接口实现(Lambda 表达式简化代码,替代匿名内部类)
return container -> {
// 1. 基础配置:端口、上下文路径
container.setPort(8081); // 端口
container.setContextPath("/demo"); // 上下文路径
container.setSessionTimeout(30, java.util.concurrent.TimeUnit.MINUTES); // Session 超时(30 分钟)

// 2. 针对 Tomcat 的特殊定制(需强转为 Tomcat 容器工厂)
if (container instanceof TomcatEmbeddedServletContainerFactory) {
TomcatEmbeddedServletContainerFactory tomcatFactory = (TomcatEmbeddedServletContainerFactory) container;
// 配置 Tomcat 最大线程数
tomcatFactory.addConnectorCustomizers(connector -> {
connector.setMaxThreads(200);
connector.setMinSpareThreads(20);
});
// 配置自定义错误页面(404 页面)
tomcatFactory.addErrorPages(new org.springframework.boot.context.embedded.ErrorPage(
org.springframework.http.HttpStatus.NOT_FOUND, "/404.html"
));
}
};
}
}
2. 关键能力:容器类型判断与特殊定制

不同容器的 API 不同,可通过 instanceof 判断容器类型,实现专属定制:

  • Tomcat:强转为 TomcatEmbeddedServletContainerFactory,定制线程数、错误页面、连接器;
  • Jetty:强转为 JettyEmbeddedServletContainerFactory,定制线程池、连接器;
  • Undertow:强转为 UndertowEmbeddedServletContainerFactory,定制 IO 线程、工作线程。
3. 示例:定制 Jetty 容器(最大 POST 大小)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
public EmbeddedServletContainerCustomizer jettyCustomizer() {
return container -> {
if (container instanceof JettyEmbeddedServletContainerFactory) {
JettyEmbeddedServletContainerFactory jettyFactory = (JettyEmbeddedServletContainerFactory) container;
// 配置 Jetty 最大 POST 请求大小(10MB)
jettyFactory.addServerCustomizers(server -> {
org.eclipse.jetty.servlet.ServletContextHandler contextHandler =
(org.eclipse.jetty.servlet.ServletContextHandler) server.getHandler();
contextHandler.setMaxFormContentSize(10 * 1024 * 1024); // 10MB
});
}
};
}

替换嵌入式 Servlet 容器(Tomcat 转 Jetty/Undertow)

默认容器是 Tomcat,若需替换为 Jetty 或 Undertow,只需通过 Maven/Gradle 调整依赖(排除 Tomcat,引入目标容器),Spring Boot 会自动识别并使用新容器。

场景 1:替换为 Jetty 容器

Jetty 轻量级、启动快,适合微服务或轻量级应用。

1. Maven 依赖配置(排除 Tomcat,引入 Jetty)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 引入 Web Starter(默认依赖 Tomcat,需排除) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除 Tomcat 依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- 引入 Jetty 容器依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 引入 Web Starter,排除 Tomcat -->
<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>
2. Undertow 专属配置(优化性能)

在配置文件中添加 Undertow 性能优化配置:

1
2
3
4
5
6
7
server:
port: 8080
undertow:
io-threads: 4 # IO 线程数(处理连接,默认 CPU 核心数,不宜过大)
worker-threads: 20 # 工作线程数(处理业务逻辑,建议为 IO 线程数的 5-10 倍)
buffer-size: 1024 # 缓冲区大小(字节)
direct-buffers: true # 使用直接内存(提升性能,减少 GC)
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
2
3
4
5
6
7
8
9
10
11
// Tomcat 自动配置类
@Configuration
@ConditionalOnClass({Servlet.class, Tomcat.class}) // 类路径存在 Tomcat 核心类才加载
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public class TomcatAutoConfiguration {
// 注册 Tomcat 容器工厂 Bean
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}

同理,Jetty 和 Undertow 也有对应的自动配置类(JettyAutoConfigurationUndertowAutoConfiguration),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 容器的定制核心是 “按需选择配置方式”:

  1. 简单配置:优先使用配置文件(server 前缀属性),开发效率高,覆盖 80% 场景;
  2. 复杂定制:通过 EmbeddedServletContainerCustomizer 编写代码,支持动态配置、容器专属定制;
  3. 容器替换:调整 Maven 依赖(排除 Tomcat,引入 Jetty/Undertow),Spring Boot 自动识别并切换

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10