人人都会AI编程

7.4 内嵌 Servlet 容器(Tomcat)启动原理

更新时间:2026-07-10

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 环境使用的上下文实现),调用链路如下:

  1. 上下文刷新refresh() 方法执行至 onRefresh()
  2. onRefresh() → 调用 createWebServer()
  3. createWebServer() → 从容器中获取 ServletWebServerFactory Bean,收集所有实现了 ServletContextInitializer 的 Bean(包括 DispatcherServletRegistrationBean 等)。
  4. factory.getWebServer(initializers) → 创建 Tomcat 实例,设置连接器端口,创建 StandardHostStandardContext,并依次调用每个 initializeronStartup 方法进行组件的动态注册。
  5. 返回 TomcatWebServer,此时 Tomcat 实例已完成配置但尚未启动。
  6. 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

这些属性会被绑定到 ServerPropertiesTomcatServletWebServerFactory 所对应的属性对象上,最终在创建 ConnectorContext 时生效。

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 容器自身是如何被组装和刷新的。