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方法。