一、核心概念

1.1 什么是内嵌 Tomcat?

  • 内嵌 (Embedded):指 Tomcat 服务器的运行实例被打包在 Spring Boot 应用的 JAR/WAR 文件内部,与应用代码一起启动和停止。
  • 无需外部安装:开发者无需在服务器上单独安装和配置 Tomcat。
  • 简化部署:应用通过 java -jar your-app.jar 即可启动,包含 Web 服务器和应用逻辑。

1.2 Spring Boot 如何集成 Tomcat?

  1. 依赖驱动:当项目引入 spring-boot-starter-web 依赖时,它会自动引入 spring-boot-starter-tomcat
  2. 自动配置spring-boot-autoconfigure 模块中的 ServletWebServerFactoryAutoConfiguration 类会检测到 Tomcat 相关的类在 classpath 中,自动配置一个 TomcatServletWebServerFactory
  3. 启动SpringApplication 在启动时,通过 ServletWebServerFactory 创建并启动内嵌的 Tomcat 实例。

1.3 关键组件

组件 说明
TomcatServletWebServerFactory 核心工厂类,负责创建和配置 Tomcat 实例。
Tomcat Apache Tomcat 的 Java 对象实例。
Connector Tomcat 的连接器,负责处理网络请求(HTTP/HTTPS)。一个 Tomcat 可以有多个 Connector。
ServletWebServerApplicationContext Spring 应用上下文,负责启动和管理内嵌 Web 服务器。
EmbeddedWebApplicationContext 老版本上下文(已过时),新版本为 ServletWebServerApplicationContext

1.4 与传统部署 (WAR + 外部 Tomcat) 的对比

特性 内嵌 Tomcat (JAR) 外部 Tomcat (WAR)
部署方式 java -jar app.jar 将 WAR 包部署到已安装的 Tomcat webapps 目录
运维复杂度 高(需管理 Tomcat 服务)
资源占用 每个应用独立进程 多个应用共享同一个 Tomcat 进程
隔离性 高(进程级隔离) 低(应用级隔离,可能相互影响)
启动速度 相对较慢
配置灵活性 高(可通过代码和配置文件) 依赖外部 Tomcat 配置 (server.xml)
适用场景 微服务、云原生应用 传统单体应用、已有 Tomcat 环境

结论现代 Spring Boot 应用推荐使用内嵌 Tomcat + JAR 部署


二、详细操作步骤

步骤 1:创建 Spring Boot 项目(确保包含 Web 依赖)

  1. 使用 Spring Initializr

    • Group: com.example
    • Artifact: demo-web
    • Dependencies: Spring Web (spring-boot-starter-web)
    • Packaging: Jar (默认)
  2. 检查 pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- spring-boot-starter-web 会 transitively 引入
             spring-boot-starter-tomcat -->
    </dependencies>
    
  3. 主启动类

    // DemoWebApplication.java
    @SpringBootApplication
    public class DemoWebApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoWebApplication.class, args);
        }
    }
    

步骤 2:基础配置(通过 application.yml

这是最常用的方式,通过配置文件修改 Tomcat 的基本属性。

# application.yml
server:
  port: 8080 # Tomcat 监听的端口
  servlet:
    context-path: /api # 应用上下文路径,访问时需要加上 /api
  tomcat:
    # 连接器 (Connector) 配置
    uri-encoding: UTF-8 # URI 编码
    max-threads: 200 # 最大工作线程数 (默认 200)
    min-spare-threads: 10 # 最小空闲线程数 (默认 10)
    max-connections: 8192 # 最大连接数 (默认 8192)
    accept-count: 100 # 等待队列长度 (当所有线程忙时,新连接进入队列)
    # 启用 HTTP/2 (需要 HTTPS)
    # http2:
    #   enabled: true
    # 访问日志
    accesslog:
      enabled: true # 启用访问日志
      pattern: common # 日志格式: common, combined, 或自定义
      # pattern: '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %Dms'
      # %Dms 表示请求处理时间 (毫秒)
      directory: /var/log/tomcat # 日志目录
      file-date-format: .yyyy-MM-dd # 按天分割日志文件
    # 远程 IP 防火墙 (可选)
    # remoteip:
    #   remote-ip-header: x-forwarded-for
    #   protocol-header: x-forwarded-proto

步骤 3:配置 HTTPS

  1. 生成或获取 SSL 证书

    # 生成自签名证书 (仅用于测试)
    keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 -storetype PKCS12 \
      -keystore keystore.p12 -validity 3650 -storepass changeit -keypass changeit
    # 将 keystore.p12 放在 src/main/resources 目录下
    
  2. 配置 application.yml

    server:
      port: 8443
      ssl:
        enabled: true
        key-store: classpath:keystore.p12 # 证书路径
        key-store-password: changeit # 证书密码
        key-store-type: PKCS12 # 证书类型
        key-alias: tomcat # 证书别名
        # client-auth: need # 双向认证 (可选)
      # 如果需要同时支持 HTTP 和 HTTPS,配置重定向
      # tomcat:
      #   redirect-contextual-error-page: true
    
  3. 访问https://localhost:8443/api/...

步骤 4:通过 Java 代码配置(高级)

当配置文件无法满足需求时,可以创建 WebServerFactoryCustomizer Bean。

// TomcatConfig.java
@Configuration
public class TomcatConfig {

    @Value("${server.max-http-header-size:8192}")
    private int maxHttpHeaderSize;

    /**
     * 配置 Tomcat 连接器 (Connector)
     */
    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatConnectorCustomizer() {
        return factory -> factory.addConnectorCustomizers(connector -> {
            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();

            // 设置最大 HTTP Header 大小 (字节)
            protocol.setMaxHttpHeaderSize(maxHttpHeaderSize);

            // 设置连接超时 (毫秒)
            connector.setConnectionTimeout(20000);

            // 设置最大 POST 大小 (字节), -1 表示无限制
            connector.setMaxPostSize(2097152); // 2MB

            // 设置 URI 编码
            connector.setURIEncoding("UTF-8");

            // 启用压缩 (可选)
            // connector.setProperty("compression", "on");
            // connector.setProperty("compressionMinSize", "2048");
            // connector.setProperty("compressableMimeType", "text/html,text/xml,text/plain,text/css,text/javascript,application/javascript");
        });
    }

    /**
     * 配置 Tomcat 本身 (Engine, Host, Context)
     */
    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
        return factory -> {
            factory.addContextCustomizers(context -> {
                // 禁用会话持久化 (对于无状态应用)
                context.setManager(null);

                // 设置会话超时 (分钟)
                context.setSessionTimeout(30);

                // 添加 Valve (如访问日志 Valve,但通常用配置文件更好)
                // AccessLogValve accessLogValve = new AccessLogValve();
                // accessLogValve.setDirectory("/var/log/tomcat");
                // accessLogValve.setPrefix("access_log");
                // accessLogValve.setSuffix(".log");
                // accessLogValve.setPattern("common");
                // context.getPipeline().addValve(accessLogValve);
            });
        };
    }

    /**
     * 配置额外的 Connector (例如同时监听 HTTP 8080 和 HTTPS 8443)
     */
    @Bean
    @ConditionalOnProperty(name = "server.ssl.enabled", havingValue = "true")
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> additionalConnectorCustomizer() {
        return factory -> factory.addAdditionalTomcatConnectors(createStandardConnector());
    }

    private Connector createStandardConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(8080); // HTTP 端口
        connector.setSecure(false);
        // 可以配置重定向到 HTTPS
        // connector.setRedirectPort(8443);
        return connector;
    }
}

步骤 5:配置文件上传大小限制

# application.yml
spring:
  servlet:
    multipart:
      enabled: true # 启用文件上传
      max-file-size: 10MB # 单个文件最大大小
      max-request-size: 50MB # 整个 HTTP 请求最大大小 (包含多个文件)
      location: /tmp # 上传文件的临时目录

步骤 6:启用 GZIP 压缩

# application.yml
server:
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
    min-response-size: 1024 # 最小响应体大小 (字节),超过才压缩

步骤 7:打包和运行

  1. 打包

    mvn clean package # 生成 target/demo-web.jar
    
  2. 运行

    java -jar demo-web.jar
    # 或指定配置文件
    java -jar demo-web.jar --spring.profiles.active=prod
    
  3. 验证

    • 访问 http://localhost:8080/api/hello (假设有一个 /hello 接口)。
    • 查看日志,确认 Tomcat 启动成功。

三、常见错误与解决方案

错误现象 原因分析 解决方案
端口被占用 1. server.port 指定的端口已被其他进程使用
2. 应用未完全退出,端口未释放
1. 更改 server.port
2. netstat -ano | findstr :8080 (Windows) / lsof -i :8080 (Mac/Linux) 找到进程并杀死 (kill -9 <pid>)
启动失败,提示找不到 Tomcat 类 1. 项目是 war 打包,但依赖了 spring-boot-starter-tomcat 且作用域为 provided
2. 依赖冲突
1. 如果是 WAR 部署到外部 Tomcat,确保 spring-boot-starter-tomcat 的 scope 为 provided
2. 检查 mvn dependency:tree,排除冲突依赖
HTTP 413 (Payload Too Large) 上传文件超过限制 1. 检查 spring.servlet.multipart.max-file-sizemax-request-size
2. 检查 Tomcat maxPostSize (可通过代码配置)
HTTP 400 (Bad Request) - Header too large HTTP Header 过大 通过 WebServerFactoryCustomizer 增加 maxHttpHeaderSize
HTTPS 无法访问 1. 证书配置错误 (key-store, password)
2. 浏览器不信任自签名证书
1. 检查 application.yml 中的 SSL 配置
2. 导入证书到浏览器信任列表,或使用可信 CA 签发的证书
访问日志不生成 1. server.tomcat.accesslog.enabled 未设置为 true
2. 日志目录无写入权限
1. 确认配置已启用
2. 检查 /var/log/tomcat 目录权限 (chmod 755 /var/log/tomcat)

四、注意事项

  1. JAR vs WAR
    • JAR: 使用内嵌 Tomcat,spring-boot-starter-tomcat 为默认依赖。
    • WAR: 部署到外部 Tomcat,需将 spring-boot-starter-tomcat 的 scope 设为 provided,并让主类继承 SpringBootServletInitializer
  2. 配置优先级
    • 命令行参数 > application.yml > @Configuration 类中的 @Bean > 默认值。
  3. 线程模型
    • 默认使用 NIO (非阻塞 I/O),性能优于 BIO
    • max-threads 不宜设置过大,避免线程上下文切换开销。
  4. 安全
    • 生产环境 HTTPS 是必须的。
    • 避免使用默认的 server.servlet.session.cookie.name (如 JSESSIONID),可自定义。
  5. 监控
    • 结合 Spring Boot Actuator (/actuator/metrics/tomcat.*) 监控 Tomcat 状态(线程数、请求量、错误率)。
  6. server.tomcat.basedir
    • 指定 Tomcat 工作目录(用于存放临时文件、日志等),默认为系统临时目录。建议显式设置。

五、使用技巧

5.1 动态修改端口

# 启动时指定
java -jar app.jar --server.port=9090

# 或通过环境变量
export SERVER_PORT=9090
java -jar app.jar

5.2 配置多个 Connector

如上文代码所示,通过 addAdditionalTomcatConnectors 添加 HTTP 和 HTTPS 连接器。

5.3 自定义 ErrorPage

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> errorPageCustomizer() {
    return factory -> factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"));
}

5.4 禁用内嵌 Tomcat(使用 Undertow 或 Jetty)

<!-- pom.xml -->
<dependencies>
    <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>
    <!-- 或使用 Jetty -->
    <!-- <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency> -->
</dependencies>

5.5 获取 Tomcat 实例信息

@Component
public class TomcatInfoPrinter implements ApplicationListener<WebServerInitializedEvent> {

    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        if (event.getWebServer() instanceof TomcatWebServer) {
            TomcatWebServer tomcatWebServer = (TomcatWebServer) event.getWebServer();
            Tomcat tomcat = tomcatWebServer.getTomcat();
            Connector connector = tomcat.getService().getConnectors()[0];
            System.out.println("Tomcat Port: " + connector.getLocalPort());
            System.out.println("Tomcat Server: " + tomcat.getServer());
        }
    }
}

六、最佳实践

  1. 使用 JAR 部署:简化部署和运维。
  2. HTTPS 强制:生产环境必须使用 HTTPS。
  3. 合理配置线程:根据应用负载和服务器资源设置 max-threadsaccept-count
  4. 启用访问日志:用于审计、分析流量和排查问题。
  5. 配置文件上传限制:防止恶意大文件上传。
  6. 启用 GZIP 压缩:减少网络传输量。
  7. 监控与告警:集成 Actuator + Prometheus + Grafana 监控 Tomcat 指标。
  8. 安全加固:隐藏服务器版本信息(通过 server.tomcat.redirect-contextual-error-page 或自定义错误页)。
  9. 优雅停机:配置 server.shutdown=graceful (Spring Boot 2.3+),让 Tomcat 在收到关闭信号后等待正在处理的请求完成。
  10. 使用配置中心:将 application.yml 中的敏感配置(如端口、证书密码)放到 Nacos/Config Server。

七、性能优化

  1. 线程池调优
    • max-threads: 根据 CPU 核心数和应用 I/O 特性设置。通常 200-800。
    • min-spare-threads: 保持一定数量的空闲线程,避免频繁创建销毁。
    • accept-count: 队列过长可能导致请求堆积,过短可能导致连接拒绝。根据负载调整。
  2. 连接超时
    • connection-timeout: 避免连接长时间占用线程。设置为 20-30 秒。
  3. 禁用不必要的功能
    • 如果应用无状态,context.setManager(null) 禁用会话管理。
    • 禁用 JMX 如果不需要监控。
  4. GZIP 压缩
    • 对文本类响应(HTML, CSS, JS, JSON)启用压缩,显著减少传输时间。
  5. JVM 调优
    • 合理设置堆内存 (-Xms, -Xmx)。
    • 选择合适的 GC 算法(如 G1GC)。
  6. 操作系统优化
    • 增加文件描述符限制 (ulimit -n)。
    • 调整 TCP 参数(如 tcp_tw_reuse)。
  7. 使用 NIO2 (APR/native)
    • Tomcat APR 库(基于 JNI)在高并发下性能优于 NIO。但需要安装 native 库,配置复杂,通常 NIO 足够。

总结

Spring Boot 内嵌 Tomcat 极大地简化了 Web 应用的开发和部署。

核心要点

  1. 依赖驱动spring-boot-starter-web 自动引入 Tomcat。
  2. 配置方式:优先使用 application.yml,复杂需求用 WebServerFactoryCustomizer
  3. 关键配置:端口、线程、HTTPS、访问日志、文件上传、压缩。
  4. 生产实践:HTTPS、监控、日志、安全、性能调优。

通过本指南,你已掌握 Spring Boot 内嵌 Tomcat 的核心配置方法。记住,JAR 部署 + 配置文件 + Actuator 监控是现代 Spring Boot 应用的标准实践。