0.
0.0. 历史文章整理
玩转 Spring Boot 入门篇
玩转 Spring Boot 集成篇(MySQL、Druid、HikariCP)
玩转 Spring Boot 集成篇(MyBatis、JPA、事务支持)
玩转 Spring Boot 集成篇(Redis)
玩转 Spring Boot 集成篇(Actuator、Spring Boot Admin)
玩转 Spring Boot 集成篇(RabbitMQ)
玩转 Spring Boot 集成篇(@Scheduled、静态、动态定时任务)
玩转 Spring Boot 集成篇(任务动态管理代码篇)
玩转 Spring Boot 集成篇(定时任务框架Quartz)
玩转 Spring Boot 原理篇(源码环境搭建)
玩转 Spring Boot 原理篇(核心注解知多少)
玩转 Spring Boot 原理篇(自动装配前凑之自定义Starter)
玩转 Spring Boot 原理篇(自动装配源码剖析)
玩转 Spring Boot 原理篇(启动机制源码剖析)
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服务。
代码语言:javascript复制@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 的启动、并发布上下文刷新的事件。
代码语言:javascript复制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() 方法。
代码语言:javascript复制@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()
默认没有开启优雅停机,如果在配置中开启优雅停机,则走的是优雅停机分支。
代码语言:javascript复制## 开启优雅停机, 如果不配置是默认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 完成服务的停止,此时控制台输出如下。
代码语言:javascript复制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 持有 webServer 对象,最后调用持有的 X xxWebServer 对象的 start、stop 等方法来完成容器的启停操作。
本次就分享到这里,这种内嵌 Tomcat 的实现方式,对于轮子的开发或许有点帮助,你们 get 到了没?
世上没有轻而易举就能获得的成功。所有出众的背后,都有着超乎寻常的努力。真正能够登顶远眺的,都是那些心无旁骛、坚持着往前走的人。再坚持一下,也许你离成功就差那一步。
一起聊技术、谈业务、喷架构,少走弯路,不踩大坑,会持续输出更多精彩分享,敬请期待!
参考资料:
https://spring.io/
https://start.spring.io/
https://spring.io/projects/spring-boot
https://github.com/spring-projects/spring-boot
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
https://stackoverflow.com/questions/tagged/spring-boot
《Spring Boot实战》《深入浅出Spring Boot 2.x》
《一步一步学Spring Boot:微服务项目实战(第二版)》
《Spring Boot揭秘:快速构建微服务体系》