内嵌服务器
一句话定位:Spring Boot 将 Tomcat、Jetty、Undertow 等 Servlet 容器直接嵌入应用内部,通过
java -jar即可启动完整 Web 服务,彻底省去外部 WAR 部署和容器管理的繁琐步骤。
定义与作用
在传统的 SSM(Spring + Spring MVC + MyBatis)开发模式中,应用必须打包为 WAR 文件,然后部署到外部 Tomcat 的 webapps 目录中,再启动 Tomcat 服务。这一过程涉及:修改 server.xml、处理上下文路径、解决类加载冲突、管理容器版本等重复劳动。
Spring Boot 的**内嵌服务器(Embedded Server)**机制将 Servlet 容器作为普通依赖引入,使其随 Spring 应用上下文一同创建和销毁。应用从"部署到容器"变为"容器即应用"。
| 对比维度 | 传统 SSM 外置容器 | Spring Boot 内嵌容器 |
|---|---|---|
| 打包格式 | WAR 文件 | 可执行 JAR(Fat JAR) |
| 部署操作 | 复制 WAR 到 Tomcat/webapps | java -jar app.jar |
| 端口配置 | 修改 server.xml | application.yml 中 server.port |
| 容器版本管理 | 运维团队统一维护 | Maven 依赖控制,应用自带 |
| 启动速度 | 容器先启动,再加载应用 | 应用与容器同时初始化,更快 |
| 微服务适配 | 一个 Tomcat 多应用,耦合高 | 一个应用一个进程,天然隔离 |
适用范围:Spring Boot 2.7.x 默认内嵌 Tomcat 9.x(支持 Servlet 4.0)。可通过替换 Starter 切换为 Jetty 或 Undertow,无需修改业务代码。
适用位置与常用属性
内嵌服务器由 spring-boot-starter-web 自动引入,开发者无需手动编写代码触发。所有配置通过 application.yml 完成。
常用配置属性
| 属性 | 说明 | 示例值 |
|---|---|---|
server.port | HTTP 监听端口 | 8080、0(随机端口) |
server.address | 绑定网络地址 | 127.0.0.1、0.0.0.0 |
server.servlet.context-path | 应用上下文根路径 | /student |
server.tomcat.threads.max | Tomcat 最大线程数 | 200 |
server.tomcat.threads.min-spare | Tomcat 最小空闲线程 | 10 |
server.tomcat.max-connections | 最大连接数 | 10000 |
server.tomcat.connection-timeout | 连接超时 | 60s |
server.compression.enabled | 响应压缩开关 | true |
server.http2.enabled | HTTP/2 支持 | true(需配合 TLS) |
切换容器的依赖方式
<!-- 默认:Tomcat(无需额外配置) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 切换为 Jetty:排除 Tomcat,引入 Jetty Starter -->
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!-- 切换为 Undertow:高性能、低内存占用 -->
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
核心原理
内嵌服务器启动时序
Spring Boot 应用在 SpringApplication.run() 过程中,通过 ServletWebServerApplicationContext 自动探测并创建内嵌 Web 服务器。以下是关键时序:
时序解读:
- 当 Spring Boot 探测到类路径存在
ServletWebServerFactory(由spring-boot-starter-web引入)时,自动判定为 Web 应用类型。 - 在上下文刷新阶段,触发
onRefresh()钩子,向容器索取WebServerFactoryBean。 TomcatServletWebServerFactory负责实例化Tomcat对象,配置Connector(端口、协议)、Host、Context。DispatcherServlet被自动注册到 Servlet 容器中,映射路径为/。- 服务器启动后,应用进入就绪状态,即可接收 HTTP 请求。
三种容器的层次对比
选型建议:
- Tomcat:默认选择,生态最成熟,社区资料最多。
- Jetty:适合长连接、WebSocket 场景,线程模型更灵活。
- Undertow:基于 XNIO 的非阻塞 IO,内存占用最低,吞吐量高,适合高并发微服务。
完整示例
场景说明
飞翔科技的学生成绩管理系统需要独立部署到一台测试服务器上。架构师白歌要求后端开发小崔:应用必须以可执行 JAR 运行,监听 8080 端口,上下文路径为 /student,且支持响应压缩。小崔需要验证内嵌 Tomcat 的启动效果。
操作前:无配置状态
# 操作前:application.yml(空文件或仅有数据库配置)
spring:
datasource:
url: jdbc:mysql://localhost:3306/student_db
username: root
password: root
此时启动应用,Spring Boot 使用全部默认值:端口 8080、上下文路径 /、无压缩。访问 http://localhost:8080/ 直接命中根路径。
启动日志:
Tomcat started on port(s): 8080 (http) with context path ''
使用该特性的完整代码
小崔在 application.yml 中补充服务器配置:
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/student_db
username: root
password: root
server:
port: 8080
servlet:
context-path: /student
compression:
enabled: true
mime-types: application/json,application/xml,text/html
tomcat:
threads:
max: 100
min-spare: 10
业务代码无需任何改动。启动类保持标准写法:
package com.feixiang.student;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StudentManagementApplication {
public static void main(String[] args) {
SpringApplication.run(StudentManagementApplication.class, args);
}
}
操作后运行结果及分析
启动日志:
Tomcat started on port(s): 8080 (http) with context path '/student'
Started StudentManagementApplication in 2.456 seconds
验证端口监听:
$ curl http://localhost:8080/student/actuator/health
{"status":"UP"}
分析:
server.port=8080生效:Tomcat 在 8080 端口启动监听。server.servlet.context-path=/student生效:所有端点前缀自动变为/student,实现应用级隔离。server.compression.enabled=true生效:返回大于阈值的 JSON/HTML 响应时自动使用 Gzip 压缩。server.tomcat.threads.max=100生效:Tomcat 线程池上限被限制为 100,避免资源耗尽。
易错场景与面试考点
易错场景一:端口冲突导致启动失败
小崔在本地同时启动了两个 Spring Boot 服务,第二个应用报错:
Web server failed to start. Port 8080 was already in use.
错误处理:小崔直接修改代码中的 server.port 为固定值,但提交时不小心将这段配置推到了 Git,导致测试环境也用了非标准端口。
正确做法:开发环境使用临时命令行参数覆盖,不改动代码:
java -jar student-app.jar --server.port=8081
或在 application.yml 中利用随机端口做本地测试:
server:
port: ${random.int[8080,8090]}
易错场景二:切换 Jetty 时未排除 Tomcat 导致冲突
<!-- 错误示范:同时存在 Tomcat 和 Jetty Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
后果:类路径中同时存在两个 ServletWebServerFactory 实现(Tomcat 和 Jetty),Spring Boot 无法判断应使用哪个,启动时抛出 IllegalStateException: Multiple ServletWebServerFactory beans found。
正确做法:切换容器时必须使用 <exclusions> 排除默认的 spring-boot-starter-tomcat。
面试考点
Q:Spring Boot 内嵌 Tomcat 是如何启动的?
SpringApplication.run()创建ServletWebServerApplicationContext,在上下文刷新阶段通过onRefresh()方法从容器中获取WebServerFactoryBean(如TomcatServletWebServerFactory),由工厂创建并启动内嵌服务器,同时自动注册DispatcherServlet。
Q:内嵌服务器和传统外置 Tomcat 有什么区别?
内嵌服务器的生命周期由 Spring 容器管理,随应用启停;应用与容器版本绑定在 JAR 中,一个应用一个进程。外置 Tomcat 多应用共享同一容器,版本由运维统一控制,部署需手动复制 WAR。
Q:server.port=0 有什么效果?
启动时由操作系统分配一个临时可用端口,适合并行测试或多实例启动。实际端口值可通过
Environment或@LocalServerPort注入获取。
Q:Spring Boot 2.7.x 默认内嵌的是 Tomcat 几?
默认内嵌 Tomcat 9.x(对应 Servlet 4.0 规范)。若切换 Jetty 则对应 Jetty 9.4.x,切换 Undertow 则对应 Undertow 2.2.x。