SpringBoot关闭时都做了哪些事?

简介: SpringBoot关闭时都做了哪些事?

本文我们开始分析SpringBoot关闭的时候都做了哪些事。核心流程梳理如下:

  • 从LiveBeansView移除掉维护的上下文
  • 广播ContextClosedEvent事件
  • 触发生命周期处理器的onClose方法,这里会stopBean,也就是触发那些Lifecycle实例的stop方法
  • 销毁BeanFactory中的所有DisposableBean并清空一些缓存
  • 关闭BeanFactory
  • 停止服务,这里会stop Tomcat
  • 将earlyApplicationListeners 数据拷贝给applicationListeners
  • 设置active状态为false

如下所示触发ConfigurableApplicationContext的close方法时就会触发服务的关闭。

public static void main(String[] args) {
    ConfigurableApplicationContext applicationContext = SpringApplication.run(RecommendApplication.class, args);
    applicationContext.close();
}

AbstractApplicationContext的close方法

如下所示,其会将具体动作委派给doClose方法,然后尝试移除掉JVM shutdown hook。

@Override
public void close() {
  synchronized (this.startupShutdownMonitor) {
    doClose();
    // If we registered a JVM shutdown hook, we don't need it anymore now:
    // We've already explicitly closed the context.
    if (this.shutdownHook != null) {
      try {
      // 移除钩子
        Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
      }
      catch (IllegalStateException ex) {
        // ignore - VM is already shutting down
      }
    }
  }
}

【1】核心关闭方法

AbstractApplicationContext的doClose

protected void doClose() {
  // Check whether an actual close attempt is necessary...
  // 如果当前应用是激活状态且没有关闭,才执行if方法体
  if (this.active.get() && this.closed.compareAndSet(false, true)) {
    if (logger.isDebugEnabled()) {
      logger.debug("Closing " + this);
    }
  // 从LiveBeansView移除掉维护的上下文
    LiveBeansView.unregisterApplicationContext(this);
    try {
      // Publish shutdown event.
      // 广播ContextClosedEvent事件
      publishEvent(new ContextClosedEvent(this));
    }
    catch (Throwable ex) {
      logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
    }
    // Stop all Lifecycle beans, to avoid delays during individual destruction.
    if (this.lifecycleProcessor != null) {
      try {
// 触发生命周期处理器的onClose方法,这里会stopBean,也就是触发那些Lifecycle实例的stop方法
        this.lifecycleProcessor.onClose();
      }
      catch (Throwable ex) {
        logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
      }
    }
    // Destroy all cached singletons in the context's BeanFactory.
    // 销毁BeanFactory中的所有DisposableBean并清空一些缓存
    destroyBeans();
    // Close the state of this context itself.
    //关闭BeanFactory
    closeBeanFactory();
    // Let subclasses do some final clean-up if they wish...
    //停止服务,这里会stop Tomcat
    onClose();
    // Reset local application listeners to pre-refresh state.
    if (this.earlyApplicationListeners != null) {
      this.applicationListeners.clear();
      this.applicationListeners.addAll(this.earlyApplicationListeners);
    }
    // Switch to inactive.
    // 设置active状态为false
    this.active.set(false);
  }
}

① unregisterApplicationContext

关于LiveBeansView.unregisterApplicationContext(this),如下所示在SpringBoot启动流程中AbstractApplicationContext的finishRefresh方法中会将应用上下文注册到LiveBeansView中,在关闭过程中就会触发unregisterApplicationContext移除应用上下文。具体来讲就是

  • 从LiveBeansView的成员applicationContexts移除应用上下文
  • 触发MBeanServer的unregisterMBean方法
  • 将LiveBeansView的成员applicationName置为null

1c417874728944b29623336c37babfac.png

② publishEvent

这里会发布ContextClosedEvent事件,有如下监听器对其感兴趣:

0 = {RestartApplicationListener@10934} 
1 = {LoggingApplicationListener@10935} 
2 = {DelegatingApplicationListener@2936} 
3 = {DelegatingApplicationListener@10936} 

LoggingApplicationListener会触发this.loggingSystem.cleanUp();。本文这里并没有配置context.listener.classes属性,所以其他监听器对该事件并无作用。

③ lifecycleProcessor.onClose()

触发生命周期处理器的关闭方法,默认实例是DefaultLifecycleProcessor,其方法如下所示:

@Override
public void onClose() {
//触发Lifecycle类型的bean的stop方法
  stopBeans();
  //修改运行状态为false
  this.running = false;
}

stopBeans方法如下所示:

private void stopBeans() {
//获取生命周期bean,本文这里只有一个documentationPluginsBootstrapper
// key = "documentationPluginsBootstrapper" --DocumentationPluginsBootstrapper
  Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
  Map<Integer, LifecycleGroup> phases = new HashMap<>();
  lifecycleBeans.forEach((beanName, bean) -> {
    int shutdownPhase = getPhase(bean);
    LifecycleGroup group = phases.get(shutdownPhase);
    if (group == null) {
      group = new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false);
      phases.put(shutdownPhase, group);
    }
    group.add(beanName, bean);
  });
  if (!phases.isEmpty()) {
    List<Integer> keys = new ArrayList<>(phases.keySet());
    keys.sort(Collections.reverseOrder());
    for (Integer key : keys) {
    // 触发LifecycleGroup的stop,最终会触发生命周期bean的stop方法
      phases.get(key).stop();
    }
  }
}

如上所示首先获取所有的Lifecycle实例,然后安装shutdownPhase 进行分组,按组进行比遍历触发bean实例的stop方法。这里需要说明的时,如果bean实例有依赖bean(dependentBeans),那么会先触发这些依赖bean的销毁。

【2】 destroyBeans

AbstractApplicationContextdestroyBeans方法最终会触发DefaultListableBeanFactorydestroySingletons方法。

protected void destroyBeans() {
  getBeanFactory().destroySingletons();
}

59f325f19a8d4f9fa61b611245c9f2a1.png

DefaultListableBeanFactory的destroySingletons方法如下所示:

// DefaultListableBeanFactory
@Override
public void destroySingletons() {
  //触发父类DefaultSingletonBeanRegistry的销毁方法
  super.destroySingletons();
  //更新Set<String> manualSingletonNames
  updateManualSingletonNames(Set::clear, set -> !set.isEmpty());
  //这里会清空两个ConcurrentHashMap:
  //allBeanNamesByType和singletonBeanNamesByType
  clearByTypeCache();
}

① DefaultSingletonBeanRegistry的destroySingletons

父类DefaultSingletonBeanRegistry的destroySingletons方法如下所示,首先遍历每一个disposableBean进行销毁,然后清空一些缓存。

// DefaultSingletonBeanRegistry
public void destroySingletons() {
  if (logger.isTraceEnabled()) {
    logger.trace("Destroying singletons in " + this);
  }
  // 对一级缓存加锁,修改单例正在销毁标识为true
  synchronized (this.singletonObjects) {
    this.singletonsCurrentlyInDestruction = true;
  }
  String[] disposableBeanNames;
  synchronized (this.disposableBeans) {
    disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
  }
  // 销毁每一个实现了disposableBean接口的单例bean
  for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
    destroySingleton(disposableBeanNames[i]);
  }
// 清空三个ConcurrentHashMap
// 本文这里为空
  this.containedBeanMap.clear();
  // 存储的是  bean---被哪些bean所需要
  this.dependentBeanMap.clear();
  //存储的是
  this.dependenciesForBeanMap.clear();
  //清空单例缓存
  clearSingletonCache();
}

如上方法所示,其获取disposableBeanNames 遍历循环触发每一个bean的销毁流程,然后清空三个ConcurrentHashMap,最后清空单例缓存。

销毁单个bean

我们先看一下disposableBean的销毁,这里首先触发的DefaultListableBeanFactorydestroySingleton方法。

// DefaultListableBeanFactory
@Override
public void destroySingleton(String beanName) {
// 触发父类DefaultSingletonBeanRegistry的销毁方法
  super.destroySingleton(beanName);
  // 从manualSingletonNames这个Set中移除beanName
  removeManualSingletonName(beanName);
  //清理缓存--又看到了,是不是多余?
  clearByTypeCache();
}

父类DefaultSingletonBeanRegistry的destroySingleton方法

也就是说DisposableBean 的销毁方法其实是被父类DefaultSingletonBeanRegistry来执行的。

public void destroySingleton(String beanName) {
// 从singletonObjects、singletonFactories、earlySingletonObjects及registeredSingletons移除
  removeSingleton(beanName);
  // Destroy the corresponding DisposableBean instance.
  DisposableBean disposableBean;
//从Map<String, Object> disposableBeans移除
  synchronized (this.disposableBeans) {
    disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
  }
  //销毁bean
  destroyBean(beanName, disposableBean);
}

关于destroyBean(beanName, disposableBean);我们本文不做进一步分析,详情参考SpringBoot关闭过程中是如何销毁一个DisposableBean的?


清空单例缓存

clearSingletonCache方法如下所示,清空了一级、三级、二级缓存以及Set<String> registeredSingletons

protected void clearSingletonCache() {
  synchronized (this.singletonObjects) {
  // 清空一级缓存
    this.singletonObjects.clear();
    //清空三级缓存
    this.singletonFactories.clear();
    //清空二级缓存
    this.earlySingletonObjects.clear();
    //清空单例bean  Set
    this.registeredSingletons.clear();
    this.singletonsCurrentlyInDestruction = false;
  }
}

② updateManualSingletonNames

这里应用了jdk1.8的新特性,主要目的就是如果manualSingletonNames不为空,那么就clear见清空。

updateManualSingletonNames(Set::clear, set -> !set.isEmpty());
//DefaultListableBeanFactory
private void updateManualSingletonNames(Consumer<Set<String>> action, Predicate<Set<String>> condition) {
    if (hasBeanCreationStarted()) {
      // Cannot modify startup-time collection elements anymore (for stable iteration)
      synchronized (this.beanDefinitionMap) {
        if (condition.test(this.manualSingletonNames)) {
          Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
          action.accept(updatedSingletons);
          this.manualSingletonNames = updatedSingletons;
        }
      }
    }
    else {
      // Still in startup registration phase
      if (condition.test(this.manualSingletonNames)) {
        action.accept(this.manualSingletonNames);
      }
    }
  }

③ clearByTypeCache

这里清理的是allBeanNamesByType和singletonBeanNamesByType。

private void clearByTypeCache() {
  this.allBeanNamesByType.clear();
  // 本文这里为空
  this.singletonBeanNamesByType.clear();
}
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
  private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);
/** Map of singleton-only bean names, keyed by dependency type. */
  private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64);

71d9d5388d97461f8de65052b83094e6.png

【3】其余方法

① closeBeanFactory

AbstractRefreshableApplicationContextcloseBeanFactory方法如下所示,设置当前应用上下文维护的beanFactory的SerializationId为null,然后beanFactory指向null。

@Override
protected final void closeBeanFactory() {
  synchronized (this.beanFactoryMonitor) {
    if (this.beanFactory != null) {
      this.beanFactory.setSerializationId(null);
      this.beanFactory = null;
    }
  }
}

② onClose

这里会触发ServletWebServerApplicationContext的onClose方法,这里会stop tomcat。

@Override
protected void onClose() {
// super是AbstractApplicationContext,其是一个空方法
  super.onClose();
//触发webServer的stop方法
  stopAndReleaseWebServer();
}

ServletWebServerApplicationContext的stopAndReleaseWebServer方法

private void stopAndReleaseWebServer() {
  WebServer webServer = this.webServer;
  if (webServer != null) {
    try {
    // 触发stop方法
      webServer.stop();
      // 引用指向null
      this.webServer = null;
    }
    catch (Exception ex) {
      throw new IllegalStateException(ex);
    }
  }
}

继续往下看,这里是TomcatWebServer的stop方法。

@Override
public void stop() throws WebServerException {
  synchronized (this.monitor) {
    boolean wasStarted = this.started;
    try {
    // 修改启动标志为false
      this.started = false;
      try {
      // 停止tomcat
        stopTomcat();
        // 销毁tomcat实例
        this.tomcat.destroy();
      }
      catch (LifecycleException ex) {
        // swallow and continue
      }
    }
    catch (Exception ex) {
      throw new WebServerException("Unable to stop embedded Tomcat", ex);
    }
    finally {
      if (wasStarted) {
        containerCounter.decrementAndGet();
      }
    }
  }
}

stopTomcat方法会触发tomcat实例的stop方法进而触发server的stop方法,tomcat实例的destroy方法也会触发server的destroy方法。

public void stop() throws LifecycleException {
    getServer();
    server.stop();
}
public void destroy() throws LifecycleException {
    getServer();
    server.destroy();
    // Could null out objects here
}

③ doClose的末尾方法

如下所示,这里会将earlyApplicationListeners 的数据复制到applicationListeners中,然后将active状态设置为false。

if (this.earlyApplicationListeners != null) {
  this.applicationListeners.clear();
  this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Switch to inactive.
this.active.set(false);

④ 移除钩子

当doClose方法执行完后,如下所示最终会移除JVM shutdown hook。

4f13a84daf7f4d94bdef34811b0d25ea.png

ShutdownHooks的remove方法如下所示,会移除当前应用上下文的销毁钩子,这里移除的是Thread[SpringContextShutdownHook,5,main]2ac19af0cb914eac92a9d0f7c55fead0.png

static synchronized boolean remove(Thread hook) {
      if(hooks == null)
          throw new IllegalStateException("Shutdown in progress");
      if (hook == null)
          throw new NullPointerException();
      return hooks.remove(hook) != null;
  }

2ac19af0cb914eac92a9d0f7c55fead0.png


目录
相关文章
|
Java 应用服务中间件 数据库连接
面试官:SpringBoot如何优雅停机?
面试官:SpringBoot如何优雅停机?
1046 0
|
Kubernetes Java 应用服务中间件
Spring Boot 系列:最新版优雅停机详解
目前Spring Boot已经发展到了2.3.4.RELEASE,伴随着2.3版本的到来,优雅停机机制也更加完善了。
13200 2
|
算法 Java API
Spring Cloud Gateway简单使用
Spring Cloud Gateway简单使用
962 0
|
前端开发 网络协议 Dubbo
超详细Netty入门,看这篇就够了!
本文主要讲述Netty框架的一些特性以及重要组件,希望看完之后能对Netty框架有一个比较直观的感受,希望能帮助读者快速入门Netty,减少一些弯路。
96114 33
超详细Netty入门,看这篇就够了!
|
弹性计算 安全 API
大模型终于能“听懂”云操作了?
本文通过 MCP Server 和大模型的结合,实现云产品管理的自然语言操作,极大提升开发者的操作效率和用户体验。
724 17
|
消息中间件 中间件 Kafka
分布式事务最全详解 ,看这篇就够了!
本文详解分布式事务的一致性及实战解决方案,包括CAP理论、BASE理论及2PC、TCC、消息队列等常见方案,助你深入理解分布式系统的核心技术。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式事务最全详解 ,看这篇就够了!
|
安全 Java 网络安全
SpringBoot 优雅停止服务的几种方法
SpringBoot 优雅停止服务的几种方法
986 0
|
JavaScript
cnpm 的安装与使用
本文介绍了npm和cnpm的概念、安装nodejs的步骤,以及cnpm的安装和使用方法,提供了通过配置npm使用中国镜像源来加速包下载的替代方案,并说明了如何恢复npm默认仓库地址。
cnpm 的安装与使用
|
安全 Java Maven
MapStruct使用教程2024(高级版)
MapStruct使用教程2024(高级版)
|
JSON Java 数据格式
java读取接口返回的json数据
java读取接口返回的json数据
248 5