一、核心概念

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-size 和 max-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-threads 和 accept-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 应用的标准实践。