0.1. 好奇心害死猫
基于 Spring Boot 搭建 WEB 项目时,只需引入 spring-boot-starter-web 依赖,启动应用时便可以启动 Tomcat 对外提供 WEB 服务,如此之简单,倒是勾起了一探究竟的好奇心。
如上图示意,通过 Maven 依赖关系,能够清晰看出,在引入 spring-boot-starter-web 依赖时,默认会自动引入 spring-boot-starter-tomcat 以及 spring-boot-starter 依赖包。
看到 maven 依赖关系,仍不足解决心中疑惑,有待继续撸码来解决心中的疑惑。
-
Spring Boot 如何 实现 的 内嵌 Tomcat?
-
Spring Boot 如何 启动 内嵌的 Tomcat?
-
Spring Boot 如何 停止 内嵌的 Tomcat?
开往 Spring Boot 心脏的高铁即将发车,请大家扶稳坐好。
1. 内嵌 Tomcat 实现原理-源码剖析
在 Spring Boot 启动流程里,有 refreshContext(context) 这么一步,这一步会加载 META-INF/spring.factories 文件中配置的一系列的 XxxAutoConfiguration 类,进而来完成自动装配的动作,而 Spring Boot 内嵌 Tomcat 亦是从自动装配开始的。
-
ServletWebServerFactoryAutoConfiguration
@Configuration
(
proxyBeanMethods
=
false
)
@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
{
// ... ...
}
public
static
class
BeanPostProcessorsRegistrar
implements
ImportBeanDefinitionRegistrar
,
BeanFactoryAware
{
// ... ...
}
从源码可以了解到 导入了 EmbeddedTomcat,EmbeddedJetty,EmbeddedUndertow 服务器的支持,在用户不指定的情况下,默认情况下使用的是 Tomcat。
当自动装配功能完成之后会接着执行 ServletWebServerApplicationContext 的 onRefresh 的方法,初始化特定上下文子类中的其它特殊的 Bean。
-
AbstractApplicationContext#onRefresh
在 onRefresh 中会调用 createWebServer 创建 Web 服务,在创建 web 服务时,会调用 getWebServerFactory 获得 ServletWebServerFactory,默认创建的是 Tomcat Web服务。
@Override
protected void onRefresh() {
super.onRefresh();
try {
logger.info("【ServletWebServerApplicationContext】【onRefresh】-【开始创建 WEB 服务】");
// 开始创建 WEB 服务
createWebServer();
logger.info("【ServletWebServerApplicationContext】【onRefresh】-【方法执行完毕,创建 WEB 服务完成】");
}
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) {
ServletWebServerFactory factory = getWebServerFactory();
// 重点在这里,主要看这个方法的内部
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
其中 ServletWebServerFactory 接口主要有 JettyServletWebServerFactory、UndertowServletWebServerFactory、TomcatServletWebServerFactory 实现。
接下来仔细瞅瞅 TomcatServletWebServerFactory 对于 getWebServer 方法的实现。
-
TomcatServletWebServerFactory#getWebServer
// Tomcat 对象的初始化
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 完成 Tomcat 的 API 调用
Tomcat tomcat = new 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 = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 准备 TomcatEmbeddedContext 并设置到 Tomcat 中
prepareContext(tomcat.getHost(), initializers);
// 构建 TomcatWebServer
return getTomcatWebServer(tomcat);
}
// 获取 Tomcat 服务
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;
// 调用 TomcatWebServer#initialize 方法
initialize();
}
// 完成 Tomcat 的初始化
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
//启动服务触发初始化监听器
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
// Tomcat 的线程都是守护线程,我们创建一个阻塞非守护线程来避免立即关闭
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
在 getWebServer 方法中会完成内嵌 Tomcat API 的调用以及 TomcatServer 的构建,并完成初始化操作, 此时控制台打印如下。
至此就完成了内嵌 Tomcat 操作,接下来看看内嵌的 Tomcat 如何启动?
2. 内嵌 Tomcat 如何启动-源码剖析
在 finishRefresh() 方法中完成了上下文生命周期处理器的初始化、bean 的启动、并发布上下文刷新的事件。
protected void finishRefresh() {
// Clear context-level resource caches (such as ASM metadata from scanning).
clearResourceCaches();
// Initialize lifecycle processor for this context.
// 为此上下文初始化 lifecycle processor。
initLifecycleProcessor();
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
if (!NativeDetector.inNativeImage()) {
LiveBeansView.registerApplicationContext(this);
}
}
-
initLifecycleProcessor()
-
getLifecycleProcessor().onRefresh()
调用 DefaultLifecycleProcessor 的 onRefresh() 方法。
@Override
public void onRefresh() {
startBeans(true);
this.running = true;
}
-
DefaultLifecycleProcessor#startBeans()
-
DefaultLifecycleProcessor#start()
-
DefaultLifecycleProcessor#doStart()
-
WebServerStartStopLifecycle#start()
-
TomcatWebServer#start()
至此,Spring Boot 内嵌的 Tomcat 就启动成功了。
3. 内嵌 Tomcat 如何关闭( 优雅停机 )-源码分析
3.1. 发送关闭服务信号
当应用启动完成后,进行 kill 操作,观察程序后续的运行情况。
当接收到终止信号时,经过一连串的调用,最终会调用到 AbstractApplicationContext#doClose 方法。
-
AbstractApplicationContext#doClose()
如源码所示,会发布 shutdown 事件,紧接着调用 DefaultLifecycleProcessor#onClose 方法来停止所有的 Lifecycle Bean,例如 WebServerGracefulShutdownLifecycle、WebServerStartStopLifecycle。
-
DefaultLifecycleProcessor#onClose
从 spring.factories 读取 SpringApplicationRunListener 类实例名称,只有 EventPublishingRunListener 一个配置,所以调用 SpringApplicationRunListener 的方法,本质上调用的是 EventPublishingRunListener 这个实现类的方法。
-
DefaultLifecycleProcessor#stopBeans
-
DefaultLifecycleProcessor#stop()
-
DefaultLifecycleProcessor#doStop()
-
WebServerGracefulShutdownLifecycle#stop()
-
TomcatWebServer#shutDownGracefully()
默认没有开启优雅停机,如果在配置中开启优雅停机,则走的是优雅停机分支。
## 开启优雅停机, 如果不配置是默认IMMEDIATE, 立即停机
server.shutdown=graceful
## 优雅停机宽限期时间
spring.lifecycle.timeout-per-shutdown-phase=20s
优雅停机的分支。
当开启优雅停机时,控制台输出如下,会等待所有请求完成,然后进行 shutdown。
当优雅停机的处理完毕后,接着会处理 WebServerStartStopLifecycle 的 stop 操作。
-
DefaultLifecycleProcessor#stop
-
DefaultLifecycleProcessor#doStop
-
SmartLifecycle#stop
-
WebServerStartStopLifecycle#stop
-
TomcatWebServer#stop()
-
TomcatWebServer#stopTomcat
最终会调用 Tomcat API 完成服务的停止,此时控制台输出如下。
TomcatWebServer stop() ----
INFO 60456 --- [ionShutdownHook] s.tomcat.SampleTomcatApplication : ServletContext destroyed
Disconnected from the target VM, address: '127.0.0.1:50735', transport: 'socket'
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
4. 例行回顾
本文采取 Debug 的方式跟了一下 Spring Boot 源码,梳理了一下 Spring Boot 内嵌 Tomcat 的主线脉略,并对内嵌 Tomcat 启动、关闭进行了深入了解。
其实回头瞅瞅,Spring Boot 内嵌的 Tomcat 启动、关闭的实现方式,大体可以简化成下面的类图。
-
内嵌容器启动或关闭时,经过 DefaultLifecycleProcessor 调用一系列方法进行 最终是由 WebServerStartStopLifecycle、 WebServerGracefulShutdownLifecycle 来处理。
-
而WebServerStartStopLifecycle、 WebServerGracefulShutdownLifecycle 持有 w ebServer 对象,最后调用持有的 X
xxWebServer 对象 的 start、stop 等方法来完成容器的启停操作 。
本次就分享到这里,这种内嵌 Tomcat 的实现方式,对于轮子的开发或许有点帮助,你们 get 到了没?
世上没有轻而易举就能获得的成功。所有出众的背后,都有着超乎寻常的努力。真正能够登顶远眺的,都是那些心无旁骛、坚持着往前走的人。再坚持一下,也许你离成功就差那一步。
一起聊技术、谈业务、喷架构,少走弯路,不踩大坑, 会持续输出更多精彩分享,欢迎关注,敬请期待!