Spring Boot 的一个显著特征是可以将应用打包为可执行的 Jar 文件,并通过 java -jar 直接运行,这背后最关键的技术就是内嵌 Servlet 容器。与其说 Spring Boot“自带”一个 Tomcat,不如说是它将一个标准的 Tomcat 实例通过编程方式创建、配置、启动并纳入了 Spring 容器的生命周期。理解这一原理,有助于我们在实际工作中进行性能调优、定制容器行为和排查启动异常。
7.4.1 从外部容器到内嵌容器
传统 Java Web 应用需要先安装独立的 Tomcat,再将 War 包部署到其指定目录,容器与应用各自独立管理。内嵌容器的思路则完全逆转:应用的主方法入口直接调用 Tomcat 的 API,在同一个 JVM 进程中创建并启动一个 Tomcat 实例,然后将 DispatcherServlet 等 Web 组件注册进去。这意味着:
- 部署方式简化:从“应用服务器 + 应用”变为单一 Jar 包,启动命令统一为
java -jar。 - 开发与生产环境一致:开发时直接运行 main 方法,生产环境用同一 Jar 包,不再有容器差异。
- 资源与生命周期统一:Spring 容器与 Web 容器共存于同一 JVM,Bean 的初始化和销毁可以完整串联。
7.4.2 核心抽象:ServletWebServerFactory
Spring Boot 将内嵌容器的创建过程抽象为 ServletWebServerFactory 接口,该接口只有一个核心方法:
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
- ServletContextInitializer:是一个回调接口,用于在 Servlet 上下文(
ServletContext)初始化时动态添加 Servlet、Filter、Listener 等组件。Spring Boot 会通过该回调将核心的DispatcherServlet注册到容器中。 - WebServer:是对运行中容器的抽象,提供了
start()、stop()和端口获取等方法。
针对 Tomcat,Spring Boot 提供了 TomcatServletWebServerFactory 实现。当应用中存在 spring-boot-starter-web 依赖时,该工厂会被自动配置类注册为 Bean。来看其关键创建逻辑的简化版:
public class TomcatServletWebServerFactory
extends AbstractServletWebServerFactory
implements ServletWebServerFactory {
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat(); // 1. 创建 Tomcat 实例
File baseDir = createTempDir("tomcat"); // 2. 设置临时目录
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(DEFAULT_PROTOCOL);
connector.setPort(getPort()); // 3. 配置端口
tomcat.getService().addConnector(connector);
tomcat.setHostname(getContext().getHostname());
prepareContext(tomcat.getHost(), initializers); // 4. 创建 Context 并应用初始化器
return getTomcatWebServer(tomcat); // 5. 包装为 TomcatWebServer
}
}
从上述代码可以看出,Spring Boot 并不是通过某种“魔法”隐藏了 Tomcat,而是直接以编程方式调用 Tomcat 的内部 API 进行组装。port 参数默认从 server.port 配置读取,而 initializers 则负责向 Tomcat 的 StandardContext 中注册各种 Web 组件。
7.4.3 DispatcherServlet 如何进行注册
Spring MVC 的核心是 DispatcherServlet,在内嵌容器模式下它同样是关键角色。自动配置类 DispatcherServletAutoConfiguration 会向容器注册一个 DispatcherServletRegistrationBean:
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
DispatcherServletRegistrationBean registration =
new DispatcherServletRegistrationBean(dispatcherServlet, "/*");
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
return registration;
}
DispatcherServletRegistrationBean 实现了 ServletContextInitializer 接口,因此在容器创建时会被回调,将 DispatcherServlet 注册到 Tomcat 的 ServletContext 中。整个过程没有 web.xml,完全由 Java 配置驱动。
7.4.4 容器启动的完整调用链路
当 Spring Boot 应用执行 SpringApplication.run(...) 时,Web 容器的启动发生在 ApplicationContext 刷新过程中的 onRefresh() 阶段。具体到 ServletWebServerApplicationContext(Web 环境使用的上下文实现),调用链路如下:
- 上下文刷新 →
refresh()方法执行至onRefresh()。 onRefresh()→ 调用createWebServer()。createWebServer()→ 从容器中获取ServletWebServerFactoryBean,收集所有实现了ServletContextInitializer的 Bean(包括DispatcherServletRegistrationBean等)。factory.getWebServer(initializers)→ 创建Tomcat实例,设置连接器端口,创建StandardHost和StandardContext,并依次调用每个initializer的onStartup方法进行组件的动态注册。- 返回
TomcatWebServer,此时Tomcat实例已完成配置但尚未启动。 webServer.start()→ 实际调用tomcat.start(),触发 Tomcat 内部各组件的启动(Connector、Engine、Host、Context 等),监听端口开始接受请求。
关键时序可以用一句话概括:
Spring 容器启动 → 获取容器工厂 → 编程创建并配置 Tomcat → 注册核心 Servlet → 启动 Tomcat
7.4.5 配置定制与常见操作
了解了原理后,实际工作中可以通过多种方式影响容器的行为:
1. 通用属性配置
在 application.properties 中设置标准属性:
server.port=8080
server.servlet.context-path=/api
server.tomcat.max-threads=200
server.tomcat.connection-timeout=30000
server.tomcat.accesslog.enabled=true
这些属性会被绑定到 ServerProperties 和 TomcatServletWebServerFactory 所对应的属性对象上,最终在创建 Connector 和 Context 时生效。
2. 通过 WebServerFactoryCustomizer 编程定制
如果需要对容器进行更灵活的调整,可以实现 WebServerFactoryCustomizer<T> 接口:
@Component
public class TomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(connector -> {
connector.setProperty("maxKeepAliveRequests", "200");
});
factory.addContextCustomizers(context -> {
context.setSessionTimeout(30);
});
}
}
这种方式允许直接操作 TomcatServletWebServerFactory 暴露的原生对象,实现属性文件无法表达的精细控制。
3. 替换默认容器
将 Tomcat 替换为 Jetty 或 Undertow 只需通过排除依赖并引入对应 starter 即可,例如使用 Jetty:
<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>
由于整个创建过程都通过 ServletWebServerFactory 抽象,切换容器后应用代码完全无需变动。
7.4.6 内嵌容器的优雅关闭
生产环境中,容器需要在应用关闭时优雅地释放资源(如等待正在处理的请求完成)。Spring Boot 启动的 TomcatWebServer 实现了 GracefulShutdown 接口,当 Spring 容器接收到关闭信号时,会通过 close() 方法首先停止接收新请求,然后等待活动请求处理完毕,最后关闭 Tomcat 实例。也可以通过配置 server.shutdown=graceful 及相应的超时时间来调整行为。
内嵌容器的引入彻底改变了 Java Web 应用的交付和运行方式。掌握它内部的启动原理,不仅有助于理解 Spring Boot 的“一键启动”体验从何而来,也是进行性能排查、定制化部署的基础。下一节,我们将进一步探索 ApplicationContext 的启动细节,揭示 Spring 容器自身是如何被组装和刷新的。