以前的web项目需要webapp目录且要打成war包,再外挂Tomcat才能成功部署,但Spring Boot并没有这些配置,他是如何做到的?

在 ​​Spring Boot 自动配置及访问静态资源原理​​ 一文中讲述及演示了Spring Boot访问非webapp下的资源,所以今天主要探讨Spring Boot是如何与Tomcat结合的。

首先,pom.xml中需要引入spring-boot-starter-web依赖,然后,依旧老套路,Debug走起,第一个断点:

org.springframework.boot.SpringApplication#run(java.lang.String…)

Spring Boot 内嵌 Tomcat 原理_spring

进入refreshContext() ,中间会调用refresh(),直到调用org.springframework.context.support.AbstractApplicationContext#refresh,

Spring Boot 内嵌 Tomcat 原理_spring_02

(看过spring源码的人应该觉得这个方法中的代码似曾相识) 进入onRefresh(),中间经过多步进入org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh

Spring Boot 内嵌 Tomcat 原理_war包_03

调用了一个createWebServer(),顾名思义,创建webServer,点击去看一下做了什么事

Spring Boot 内嵌 Tomcat 原理_tomcat_04

点进去getWebServerFactory()

Spring Boot 内嵌 Tomcat 原理_war包_05

执行完后回到createWebServer(),继续下一步,先看getSelfInitializer()

Spring Boot 内嵌 Tomcat 原理_war包_06

Spring Boot 内嵌 Tomcat 原理_tomcat_07

继续看getWebServer()

Spring Boot 内嵌 Tomcat 原理_tomcat_08

这一段主要是创建tomcat相关,点进去prepareContext(),这个方法需要关注的逻辑如下:

Spring Boot 内嵌 Tomcat 原理_spring_09

然后进入configureContext()

Spring Boot 内嵌 Tomcat 原理_war包_10

执行完这个方法回到org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer中的getTomcatWebServer(tomcat)

Spring Boot 内嵌 Tomcat 原理_spring_11

/**
* Create a new {@link TomcatWebServer} instance.
* @param tomcat the underlying Tomcat server
* @param autoStart if the server should be started
*/
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}

private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + 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();
}
});

// 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
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}

执行这个方法的过程中可能会执行各种回调,执行完一系列操作后,回到org.springframework.context.support.AbstractApplicationContext#refresh中的finishRefresh()

Spring Boot 内嵌 Tomcat 原理_war包_12

在这个方法中会启动webServer,也就是启动Tomcat

Spring Boot 内嵌 Tomcat 原理_spring_13

到这里,内嵌tomcat的主要流程算是完成了,只是中间很多细节没有细讲,后续有时间再完善。。。