环境准备

新建一个maven项目,引入 spring-boot-starter-web 依赖。

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.2</version>
</dependency>

整个项目就只有如下一个类,用于SpringBoot应用程序的启动类。

1
2
3
4
5
6
@SpringBootApplication
public class SpringBootApplicationDemo {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplicationDemo.class, args);
}
}

运行内容如下:

image-20231008155355468

从运行结果可以看出,SpringBoot启动了Tomcat服务,端口为8080,版本号为9.0.65。

源码分析

SpringBoot初始化Tomcat服务的主流程

  1. 分析源码的入口就从日志出发,打印 "Tomcat initialized with port(s): 8080 (http)" 的类为 TomcatWebServer,全局搜索进入到该类,并找到这句话的代码位置。

image-20231008160529757

从图中可以看出,日志只在 initialize 方法中出现过,并且该方法只被构造方法所调用,那么就看何时调用的该构造方法。

注意观察 TomcatWebServer 的所在包(spring-boot-2.7.2.jar),说明该类是SpringBoot自己内嵌实现的,通过类的包名也可以大致猜到。org.springframework.boot是SpringBoot项目的根路径,web表示是一个web服务,embedded表示嵌入式,说明该web服务被内嵌到SpringBoot项目中了,tomcat是实现web服务的一种。

  1. 一步一步的往上看调用链,是TomcatServletWebServerFactory类的getTomcatWebServer方法实例化了 TomcatWevServer对象。

image-20231008161353363

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

image-20231008161538986

  1. getWebServer方法则是由ServletWebServerApplicationContext类的createWebServer方法调用。

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

image-20231008162124639

ServletWebServerApplicationContext的包路径为 org.springframework.boot.web.servlet.context 。

从这里开始,源码分析就分为了两条线路。第一条,getWebServerFactory是如何获取到的TomcatServletWebServerFactory对象;第二条,就是源码分析的主线路,createWebServer方法的上层调用链是哪。

为了保证主线路的思路不中断,第一条疑问先暂时忽略。

  1. createWebServer方法是由内部的onRefresh方法所调用。onRefresh方法是重写的父类GenericWebApplicationContext的方法,而父类是继承的AbstractApplicationContext抽象类。

image-20231008163113257

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

image-20231008164245201

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

image-20231008164323793

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

image-20231008165537906

AnnotationConfigServletWebServerApplicationContext类继承了ServletWebServerApplicationContext类。

回到第5步,AbstractApplicationContext的实现对象以及实现位置就算是找到了。再看何时调用的onRefresh方法。

  1. 回到第6步,图中提到了refreshContext方法,其调用了内部的refresh方法。refresh方法则又调用了ConfigurableApplicationContextrefresh方法。

image-20231008170009513

image-20231008170125128

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

image-20231008170433552

其内部又调用了父类AbstractApplicationContextrefresh方法。

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

image-20231008171426624

主流程调用链路总结如下:

-> 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

  1. getWebServerFactory方法是直接从Spring IOC容器中获取类型为ServletWebServerFactory的Bean,要求Bean的个数必须有且仅有一个。

image-20231009153300480

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

image-20231009153536791

进入到TomcatServletWebServerFactory类下,通过IDEA没法找到在何处引用到它(因为IDEA只搜索当前jar包)。

  1. 既然如此,就直接启动程序,开启debug模式,在TomcatServletWebServerFactory类的构造方法上打上断点。

image-20231009155122871

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

image-20231009155306802

  1. 观察ServletWebServerFactoryConfiguration类,它是通过条件装配扫描到了TomcatServletWebServerFactory,而Jetty和Undertow因为扫描顺序和条件判断的问题被忽略。

需要注意的是,在只引入spring-boot-starter-web依赖包的情况下,SpringBoot默认只支持Tomcat容器,Jetty和Undertow需要单独引入pom依赖。

pom依赖参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.2</version>
<exclusions>
<!-- 如果需要使用内嵌的Jetty或Undertow,就需要排除web包里面的tomcat依赖 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Jetty -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>2.7.2</version>
</dependency>
<!-- Undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
<version>2.7.2</version>
</dependency>

ApplicationContextFactory和WebApplicationType来源

出现在主流程的第7步,入口为:SpringApplication#createApplicationContext

ApplicationContextFactory

ApplicationContextFactory是一个FunctionalInterface接口类,它具有默认实现类。

image-20231009172433701

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

image-20231009172603997

Spring Boot指定了两个ApplicationContextFactory实现类,分为Reactive和Servlet。

WebApplicationType

webApplicationType变量赋值入口有两个,分别是SpringApplication的构造方法和setter方法。

image-20231009172917167

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

image-20231009173234092

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

image-20231009173327996

Tomcat服务启动过程

Tomcat服务启动主要是经历 initialize()start() 两个步骤。

initialize()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
// 维护一个实例id到Tomcat引擎中
addInstanceIdToEngineName();

// 获取Tomcat上下文
Context context = findContext();
// 给Tomcat上下文中添加一个生命周期监听器
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});

// 启动Tomcat服务器,并触发初始化侦听器
this.tomcat.start();

// 将Tomcat启动时的异常抛出。用于判断Tomcat服务器状态是否为已启动
rethrowDeferredStartupExceptions();

try {
// Tomcat上下文的缓存绑定当前ClassLoader
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}

// 创建并启动一个非守护线程。用于阻塞主线程结束
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}

通过源码分析,可以发现,Tomcat在初始化时就已经启动了服务器,并内置有生命周期监听器,还创建了一个阻塞非守护线程。

start()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void start() throws WebServerException {
synchronized (this.monitor) {
// 判断是否Tomcat服务器已启动
if (this.started) {
return;
}
try {
// 将已启动的连接从缓存移除
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
// 执行延期启动的程序
performDeferredLoadOnStartup();
}
// 检查Tomcat连接是否已启动
checkThatConnectorsHaveStarted();
// 将Tomcat服务器设置为已启动状态
this.started = true;
logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
+ getContextPath() + "'");
}
catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
PortInUseException.throwIfPortBindingException(ex, () -> this.tomcat.getConnector().getPort());
throw new WebServerException("Unable to start embedded Tomcat server", ex);
}
finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
}
}

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