前言

在Spring的时期如果我们想部署一个JAVA服务,通常是在服务器上安装一个Tomcat服务,然后将JAVA项目打好的war包丢到相应位置就好,麻烦且容易出错。所以Spring Boot中简化了这个操作,可以直接启动一个项目而不用单独部署Tomcat,那么Spring Boot是如何实现这一切的呢?

自动引入Tomcat依赖

Tomcat也是基于JAVA开发的,它本质上也是一个jar包,所以Spring Boot为它添加了默认依赖,当我们创建一个web项目时会自动将tomcat的依赖引入进来。

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

然后点进去spring-boot-starter-web,可以看到它引入了spring-boot-starter-tomcat依赖,所以后面也就可以使用Tomcat的能力了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>3.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>3.2.1</version>
<scope>compile</scope>
</dependency>
*****省略******
</dependencies>

判断应用类型

在Spring Boot启动过程中会获取应用类型,然后根据这个应用类型自动配置合适的Web环境,这在前面的文章中有提及,我们本次看下具体细节。

1
2
3
4
5
6
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
***省略前面代码***
// 就是这里
this.webApplicationType = WebApplicationType.deduceFromClasspath();
***省略后面代码***
}

可以看到系统启动时判断启动的系统属于REACTIVE项目、SERVLET项目还是NONE,我们是默认启动的,也就是SERVLET,Spring Boot就会给我们配置SERVLET环境。

1
2
3
4
5
6
7
8
9
10
11
12
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}

读取配置

我们会有一些配置项,比如说server.port=8090,这些配置项会包含在一个TomcatServletWebServerFactory的Bean中,那么它是如何被加载进来的?

简言之,在项目启动时Spring Boot的自动装配机制会装配一个ServletWebServerFactoryAutoConfiguration类,这个类维护在org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中,它引入的EmbeddedTomcat类会构建TomcatServletWebServerFactory。

1
2
3
# 省略前面内容
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
# 忽略后面内容

可以看到ServletWebServerFactoryAutoConfiguration上通过@Import注解引入了四个类,容器类的分别是EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow,我们只需要看EmbeddedUndertow即可。

1
2
3
4
5
6
7
8
9
10
@AutoConfiguration(after = SslAutoConfiguration.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {}

继续看EmbeddedTomcat类,通过@ConditionalOnClass和@ConditionalOnMissingBean两个注解可以看出,这个配置会在满足以下条件时执行:

  • 当前的classpath中包含Servlet、Tomcat和UpgradeProtocol这几个类(这通常意味着你的项目中包含了Tomcat的依赖)。
  • 当前的Spring容器中还没有ServletWebServerFactory类型的Bean。

当这个配置方法执行时,它会创建一个新的TomcatServletWebServerFactory实例,并将其注册到Spring容器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {

/**
* 构建一个新的TomcatServletWebServerFactory实例,并将其注册到Spring容器中
*/
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().toList());
factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().toList());
factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().toList());
return factory;
}
}

run()方法中的逻辑

接着就要到AbstractApplicationContext#refresh方法中的逻辑了,在这个方法中会调用onRefresh()方法,这个方法会有子类实现,在这里是ServletWebServerApplicationContext类。

ServletWebServerApplicationContext

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
@Override
protected void onRefresh() {
super.onRefresh();
try {
// 可以看到这里就是创建web服务相关的代码了
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
// 创建和启动Web服务器的具体方法
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = getApplicationStartup().start("spring.boot.webserver.create");
// 获取ServletWebServerFactory实例,就是我们第二步分析的TomcatServletWebServerFactory类
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
// 创建webServer,WebServer实例代表了一个可以接收和处理HTTP请求的Web服务器
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
// 注册优雅关闭的生命周期钩子
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
// 注册启动/停止的生命周期钩子
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) { // 处理ServletContext已经存在的情况
try {
// 在ServletContext上启动应用
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
// 初始化属性源
initPropertySources();
}

继续看getWebServer方法,这里会声明Tomcat实例,并创建connector、配置引擎、配置Context等,最后会在TomcatWebServer#initialize方法中执行this.tomcat.start()启动Tomcat。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 声明一个新的Tomcat实例
Tomcat tomcat = new Tomcat();
// 设置Tomcat的基础目录,如果没有指定,那么创建一个临时目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 添加生命周期监听器
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
// 创建一个新的Connector,并设置协议
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
// 自定义Connector
customizeConnector(connector);
tomcat.setConnector(connector);
// 禁用自动部署
tomcat.getHost().setAutoDeploy(false);
// 配置Engine
configureEngine(tomcat.getEngine());
// 添加额外的Connector
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 准备Context,即配置Tomcat的Context内容
prepareContext(tomcat.getHost(), initializers);
// 创建并返回WebServer
return getTomcatWebServer(tomcat);
}
// 可以看到这里了又构建了一个TomcatWebServer对象
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}

public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
initialize();
}
//
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();

Context context = findContext();
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();
}
});

disableBindOnInit();

// Start the server to trigger initialization listeners
// 这里启动类tomcat
this.tomcat.start();

// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();

try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}

// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startNonDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}

总结

简单来说大概可以分为以下几步

  1. 确认应用类型SERVLET,加载SERVELETweb环境
  2. createWebServer()中生成TomcatServletWebServerFactoryBean实例
  3. 调用TomcatServletWebServerFactoryBean的getWebServer()生成并启动WebServer。
    1. 创建Tomcat实例,然后配置Connector、引擎、Context等
    2. TomcatWebServer#initialize方法中执行this.tomcat.start()启动Tomcat