Spring Boot内嵌容器深度解析:Tomcat是如何被启动的?

你执行java -jar,一个Tomcat就静悄悄地起来了------这中间发生了什么?从main方法到Connector,我们来走一遍Tomcat的"复活"之路。

Spring Boot最让人惊叹的特性之一,就是它能将应用打包成一个独立的JAR包,直接通过java -jar运行,内嵌的Tomcat会自动启动。这个过程中,没有web.xml,没有独立的Tomcat安装,一切看起来像魔法。

但魔法背后是精密的工程设计。本文将从源码层面,带你走完Tomcat从"被引入"到"启动完成"的全程,看看Spring Boot是如何让Tomcat"嵌入"到应用中的。

一、从jar包到容器:内嵌Tomcat的宏观路径

在深入源码之前,我们先从宏观上理解整个流程。当你的Spring Boot应用启动时,内嵌Tomcat的诞生要经历三个阶段:

核心要点:Tomcat的创建并非在Spring Boot启动的瞬间完成,而是作为Spring容器刷新过程的一部分,在onRefresh阶段被触发。

二、自动配置:为Tomcat的诞生准备好"产房"

在Tomcat真正被创建之前,Spring Boot已经通过自动配置机制,准备好了创建Tomcat所需的工厂类。

2.1 ServletWebServerFactoryAutoConfiguration

在spring.factories中,有一个关键的自动配置类:ServletWebServerFactoryAutoConfiguration。这个类负责导入不同容器的工厂配置。

EmbeddedTomcat这个内部类的代码很关键:

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class)
public static class EmbeddedTomcat {
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(
            ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
            ObjectProvider<TomcatContextCustomizer> contextCustomizers,
            ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        // 注入各种定制器
        return factory;
    }
}

条件注解的含义:

@ConditionalOnClass:只有类路径中存在Tomcat.class,这个配置才会生效------这就是为什么引入spring-boot-starter-web会自动引入tomcat依赖

@ConditionalOnMissingBean:如果用户没有自定义ServletWebServerFactory,才使用默认的

三、核心时机:onRefresh与createWebServer

有了工厂类之后,真正的创建动作发生在什么时候?答案是Spring容器刷新的onRefresh阶段。

3.1 ServletWebServerApplicationContext

Spring Boot为Web应用准备了一个特殊的ApplicationContext实现------ServletWebServerApplicationContext。它的onRefresh方法中,调用了关键的createWebServer方法:

3.2 获取工厂类

在getWebServerFactory()方法中,Spring会从容器中查找类型为ServletWebServerFactory的Bean:

java 复制代码
protected ServletWebServerFactory getWebServerFactory() {
    // 获取所有ServletWebServerFactory类型的Bean名称
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    // 必须有且仅有一个,否则抛出异常
    if (beanNames.length == 0) {
        throw new ApplicationContextException("...");
    }
    if (beanNames.length > 1) {
        throw new ApplicationContextException("...");
    }
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

这种"必须有且仅有一个"的设计,体现了Spring Boot的约定优于配置原则。如果你不小心注入了多个工厂Bean,启动时会直接报错,避免了不确定行为。

四、Tomcat的诞生:从工厂到实例

有了工厂类,接下来就是创建真正的Tomcat实例。

4.1 TomcatServletWebServerFactory.getWebServer

TomcatServletWebServerFactory的getWebServer方法,是Tomcat诞生的地方:

4.2 构造Tomcat实例

new Tomcat()创建的是一个"骨架"实例,此时还没有启动任何组件。随后,工厂会进行一系列配置:

设置baseDir:临时目录,用于存放解压的JSP等资源

创建Connector:默认创建HTTP/1.1连接器,监听8080端口

配置Host:创建StandardHost实例,设置appBase等

应用定制器:执行用户注册的各种TomcatConnectorCustomizer、TomcatContextCustomizer等

4.3 启动Tomcat

真正的启动发生在TomcatWebServer的构造器中:

java 复制代码
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    this.tomcat = tomcat;
    initialize();
}

private void initialize() {
    // ... 省略前置处理
    start(); // 调用tomcat.start
    // ... 后置处理
}

tomcat.start()会触发Tomcat自身的生命周期:从Server开始,依次启动Service、Engine、Host、Context,最后启动Connector,开始监听端口。

五、Tomcat自身的启动流程

当调用tomcat.start()时,Tomcat内部的组件树开始启动。Tomcat的架构是分层的:

启动顺序:按照组件的树形结构,从顶层到底层,依次调用生命周期方法。Connector的启动会打开Socket端口,开始接受HTTP请求。

六、扩展与定制:如何替换为Jetty/Undertow?

理解了Tomcat的启动原理,替换其他容器就很简单了。Spring Boot的设计让容器完全可插拔。

6.1 排除Tomcat,引入Jetty

java 复制代码
<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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

6.2 原理分析

当排除Tomcat后,TomcatServletWebServerFactory的创建条件@ConditionalOnClass(Tomcat.class)就不再满足,因为Tomcat的类不存在了。同时,引入的spring-boot-starter-jetty会带来Jetty的依赖和对应的自动配置,JettyServletWebServerFactory就会生效。

整个过程完全自动化,开发者只需要改变依赖,Spring Boot负责处理剩下的逻辑。

七、总结:从jar到web服务器的完整链条

回顾整个流程,Spring Boot启动内嵌Tomcat的核心路径如下:

java 复制代码
SpringApplication.run()
        ↓
调用 run() 方法(SpringApplication.java)
        ↓
创建 ApplicationContext(AnnotationConfigServletWebServerApplicationContext)
        ↓
refreshContext() → 调用 onRefresh()
        ↓
onRefresh() → 调用 createWebServer()
        ↓
调用 ServletWebServerApplicationContext.createWebServer()
        ↓
通过 WebServerFactory 获取 TomcatServletWebServerFactory
        ↓
factory.getWebServer(ServletContextInitializer...)
        ↓
new Tomcat().getServer()
        ↓
Tomcat.start()
        ↓
内嵌 Tomcat Server 启动完成

关键设计思想:

分离接口与实现:ServletWebServerFactory定义创建Web服务器的规范,具体实现由各容器提供

条件化配置:通过@ConditionalOnClass等注解,根据类路径决定使用哪个容器

单一工厂原则:容器中只能有一个ServletWebServerFactory Bean,避免冲突

生命周期钩子:通过onRefresh将容器启动与Web服务器启动有机结合

理解了这些原理,你不仅能回答"Spring Boot怎么启动Tomcat",还能在遇到容器相关问题时快速定位------比如端口冲突、Connector定制失败、容器切换异常等。

相关推荐
小江的记录本2 小时前
【反射】Java反射 全方位知识体系(附 应用场景 + 《八股文常考面试题》)
java·开发语言·前端·后端·python·spring·面试
孟陬2 小时前
国外技术周刊 #4:这38条阅读法则改变了我的人生、男人似乎只追求四件事……
前端·人工智能·后端
没有bug.的程序员2 小时前
100%采样率引发的全线熔断:Spring Boot 链路追踪的性能绞杀与物理级调优
java·spring boot·后端·生产·熔断·调优·链路追踪
无籽西瓜a2 小时前
Linux 文件权限与 chmod 详解
linux·服务器·后端
**蓝桉**2 小时前
Keepalived+Nginx+Tomcat 高可用负载均衡
nginx·tomcat·负载均衡
thulium_2 小时前
Rust 编译错误:link.exe 未找到
开发语言·后端·rust
SimonKing2 小时前
IntelliJ IDEA 配置与插件全部迁移到其他盘,彻底释放C盘空间
java·后端·程序员
华科易迅2 小时前
Spring 代理
java·后端·spring
IT_陈寒2 小时前
SpringBoot 项目启动慢?5 个提速技巧让你的应用快如闪电 ⚡️
前端·人工智能·后端