0.
0.0. 历史文章整理
玩转 Spring Boot 集成篇(MySQL、Druid、HikariCP)
玩转 Spring Boot 集成篇(MyBatis、JPA、事务支持)
玩转 Spring Boot 集成篇(Actuator、Spring Boot Admin)
玩转 Spring Boot 集成篇(@Scheduled、静态、动态定时任务)
玩转 Spring Boot 集成篇(定时任务框架Quartz)
玩转 Spring Boot 原理篇(自动装配前凑之自定义Starter)
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 心脏的高铁即将发车,请大家扶稳坐好。
1. 内嵌 Tomcat 实现原理-源码剖析
在 Spring Boot 启动流程里,有 refreshContext(context) 这么一步,这一步会加载 META-INF/spring.factories 文件中配置的一系列的 XxxAutoConfiguration 类,进而来完成自动装配的动作,而 Spring Boot 内嵌 Tomcat 亦是从自动装配开始的。
@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。
在 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 方法的实现。
// 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 如何启动-源码剖
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);
}
}
调用 DefaultLifecycleProcessor 的 onRefresh() 方法。
@Override
public void onRefresh() {
startBeans(true);
this.running = true;
}
至此,Spring Boot 内嵌的 Tomcat 就启动成功了。
3. 内嵌 Tomcat 如何关闭(优雅停机)-源码分析
3.1. 发送关闭服务信号
当应用启动完成后,进行 kill 操作,观察程序后续的运行情况。
当接收到终止信号时,经过一连串的调用,最终会调用到 AbstractApplicationContext#doClose 方法。
如源码所示,会发布 shutdown 事件,紧接着调用 DefaultLifecycleProcessor#onClose 方法来停止所有的 Lifecycle Bean,例如 WebServerGracefulShutdownLifecycle、WebServerStartStopLifecycle。
从 spring.factories 读取 SpringApplicationRunListener 类实例名称,只有 EventPublishingRunListener 一个配置,所以调用 SpringApplicationRunListener 的方法,本质上调用的是 EventPublishingRunListener 这个实现类的方法。
默认没有开启优雅停机,如果在配置中开启优雅停机,则走的是优雅停机分支。
## 开启优雅停机, 如果不配置是默认IMMEDIATE, 立即停机
server.shutdown=graceful
## 优雅停机宽限期时间
spring.lifecycle.timeout-per-shutdown-phase=20s
优雅停机的分支。
当开启优雅停机时,控制台输出如下,会等待所有请求完成,然后进行 shutdown。
当优雅停机的处理完毕后,接着会处理 WebServerStartStopLifecycle 的 stop 操作。
最终会调用 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 启动、关闭的实现方式,大体可以简化成下面的类图。
本次就分享到这里,这种内嵌 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揭秘:快速构建微服务体系》