玩转 Spring Boot 原理篇(内嵌Tomcat实现原理&优雅停机源码剖析)

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 到了没?

世上没有轻而易举就能获得的成功。所有出众的背后,都有着超乎寻常的努力。真正能够登顶远眺的,都是那些心无旁骛、坚持着往前走的人。再坚持一下,也许你离成功就差那一步。

一起聊技术、谈业务、喷架构,少走弯路,不踩大坑, 会持续输出更多精彩分享,欢迎关注,敬请期待!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值