SpringBoot之Tomcat初始化过程的源码分析
环境准备
新建一个maven项目,引入 spring-boot-starter-web 依赖。
1 | <dependency> |
整个项目就只有如下一个类,用于SpringBoot应用程序的启动类。
1 | @SpringBootApplication |
运行内容如下:

从运行结果可以看出,SpringBoot启动了Tomcat服务,端口为8080,版本号为9.0.65。
源码分析
SpringBoot初始化Tomcat服务的主流程
- 分析源码的入口就从日志出发,打印 "Tomcat initialized with port(s): 8080 (http)" 的类为
TomcatWebServer,全局搜索进入到该类,并找到这句话的代码位置。

从图中可以看出,日志只在 initialize 方法中出现过,并且该方法只被构造方法所调用,那么就看何时调用的该构造方法。
注意观察
TomcatWebServer的所在包(spring-boot-2.7.2.jar),说明该类是SpringBoot自己内嵌实现的,通过类的包名也可以大致猜到。org.springframework.boot是SpringBoot项目的根路径,web表示是一个web服务,embedded表示嵌入式,说明该web服务被内嵌到SpringBoot项目中了,tomcat是实现web服务的一种。
- 一步一步的往上看调用链,是
TomcatServletWebServerFactory类的getTomcatWebServer方法实例化了TomcatWevServer对象。

- 而
getTomcatWebServer方法是由内部的getWebServer方法所调用,getWebServer方法在调用前,实例化一个了Tomcat对象,并作为入参传递。

-
getWebServer方法则是由ServletWebServerApplicationContext类的createWebServer方法调用。从图中可以看出,
createWebServer方法先通过内部的getWebServerFactory方法获取的TomcatServletWebServerFactory对象,再通过ServletWebServerFactory接口对象调用getWebServer方法。

ServletWebServerApplicationContext的包路径为 org.springframework.boot.web.servlet.context 。
从这里开始,源码分析就分为了两条线路。第一条,getWebServerFactory是如何获取到的TomcatServletWebServerFactory对象;第二条,就是源码分析的主线路,createWebServer方法的上层调用链是哪。
为了保证主线路的思路不中断,第一条疑问先暂时忽略。
createWebServer方法是由内部的onRefresh方法所调用。onRefresh方法是重写的父类GenericWebApplicationContext的方法,而父类是继承的AbstractApplicationContext抽象类。

- 看到
AbstractApplicationContext类,就可以猜到是Spring初始化上下文时构造的对象。所以现在就需要从SpringApplication#run入口开始分析。

createApplicationContext方法就是实例化ConfigurableApplicationContext对象的入口,这个方法使用了两个变量:applicationContextFactory和webApplicationType,对于这两个变量值的来源先暂时不管。

- 再根据猜想,
applicationContextFactory的实例对象是AnnotationConfigServletWebServerApplicationContext.Factory,其create方法实例化了AnnotationConfigServletWebServerApplicationContext对象。

AnnotationConfigServletWebServerApplicationContext类继承了ServletWebServerApplicationContext类。
回到第5步,AbstractApplicationContext的实现对象以及实现位置就算是找到了。再看何时调用的onRefresh方法。
- 回到第6步,图中提到了
refreshContext方法,其调用了内部的refresh方法。refresh方法则又调用了ConfigurableApplicationContext的refresh方法。


- 在第8步中,已经知道
ConfigurableApplicationContext的实现类是AnnotationConfigServletWebServerApplicationContext,其父类为ServletWebServerApplicationContext,所以这里的applicationContext.refresh()方法是进入到了ServletWebServerApplicationContext#refresh()方法中。

其内部又调用了父类AbstractApplicationContext的refresh方法。
AbstractApplicationContext#refresh方法调用了内部的onRefresh方法,根据方法重写,实际是调用了ServletWebServerApplicationContext#onRefresh方法。至此,就对接上了第5步的主线路。

主流程调用链路总结如下:
-> SpringApplication#run(Class<?> primarySource, String... args)
-> ConfigurableApplicationContext#refresh()
-> ServletWebServerApplicationContext#onRefresh()
-> ServletWebServerApplicationContext#createWebServer()
-> TomcatServletWebServerFactory#getWebServer(ServletContextInitializer... initializers)
-> TomcatServletWebServerFactory#getTomcatWebServer(Tomcat tomcat)
-> TomcatWebServer#initialize()
获取WebServer工厂对象
出现在主流程的第4步,入口为:ServletWebServerApplicationContext#getWebServerFactory
getWebServerFactory方法是直接从Spring IOC容器中获取类型为ServletWebServerFactory的Bean,要求Bean的个数必须有且仅有一个。

- 查看
ServletWebServerFactory的实现类,可以发现都是SpringBoot内嵌实现的,包含有Jetty、Tomcat、Undertow。

进入到TomcatServletWebServerFactory类下,通过IDEA没法找到在何处引用到它(因为IDEA只搜索当前jar包)。
- 既然如此,就直接启动程序,开启debug模式,在
TomcatServletWebServerFactory类的构造方法上打上断点。

- 查看上一层调用链,发现了一个新类
ServletWebServerFactoryConfiguration,它处在于spring-boot-autoconfigure-2.7.2.jar下。

- 观察
ServletWebServerFactoryConfiguration类,它是通过条件装配扫描到了TomcatServletWebServerFactory,而Jetty和Undertow因为扫描顺序和条件判断的问题被忽略。
需要注意的是,在只引入spring-boot-starter-web依赖包的情况下,SpringBoot默认只支持Tomcat容器,Jetty和Undertow需要单独引入pom依赖。
pom依赖参考如下:
1 | <dependency> |
ApplicationContextFactory和WebApplicationType来源
出现在主流程的第7步,入口为:SpringApplication#createApplicationContext
ApplicationContextFactory
ApplicationContextFactory是一个FunctionalInterface接口类,它具有默认实现类。

默认实现类通过Spring的SPI机制加载指定类,并根据入参值决定使用哪个类。

Spring Boot指定了两个ApplicationContextFactory实现类,分为Reactive和Servlet。
WebApplicationType
webApplicationType变量赋值入口有两个,分别是SpringApplication的构造方法和setter方法。

在本项目的示例中,采用的是最基本的SpringApplication#run方式启动,没有经过setter方法,从run方法一路debug下来,也可以发现webApplicationType是在实例化时进行的赋值操作。

再看WebApplicationType.deduceFromClasspath()方法,它其实就是通过访问指定全量路径类是否存在,判断WebApplicationType类型。

Tomcat服务启动过程
Tomcat服务启动主要是经历 initialize() 和 start() 两个步骤。
initialize()
1 | private void initialize() throws WebServerException { |
通过源码分析,可以发现,Tomcat在初始化时就已经启动了服务器,并内置有生命周期监听器,还创建了一个阻塞非守护线程。
start()
1 | public void start() throws WebServerException { |
TomcatWebServer#start()是通过Spring应用上下文的生命周期刷新事件触发的,在Spring启动阶段的finishRefresh阶段。
总结
-
SpringBoot应用程序是如何启动Tomcat服务器的?
答:在Spring启动时,通过应用上下文调用refresh方法触发Tomcat服务启动。回看SpringBoot初始化Tomcat服务的主流程章节。
-
Tomcat服务器是何时启动的?
答:在Spring启动时初始化Tomcat Web服务对象时就启动了,最后在Spring启动的finishRefresh阶段被标注为已启动。回看Tomcat服务器启动过程章节。
-
SpringBoot Web服务是怎样做到Java程序不结束退出的。
答:做到Java程序不结束退出这一点是由Web服务实现方决定的,以Tomcat举例,在Tomcat服务器初始化时,创建了一个阻塞非守护线程,阻止了主线程的退出。回看Tomcat服务启动过程章节的initialize方法。










