当前位置: 首页 > news >正文

Spring Boot 2.7 中资源销毁的先后顺序

详细解析 AbstractApplicationContext 类中的 doClose() 方法,并总结在 Spring Boot 2.7 中资源销毁的先后顺序。

方法详解

doClose() 方法是 Spring 应用上下文关闭过程的核心方法。它被设计为 protected,意味着它主要供 Spring 框架内部调用(例如,由 close() 方法调用),但子类也可以重写它(通常通过 onClose() 钩子方法)以添加自定义的关闭逻辑。

方法执行流程如下:

  1. 状态检查与原子性关闭确认

    if (this.active.get() && this.closed.compareAndSet(false, true)) {
    
    • this.active.get(): 检查上下文当前是否处于活动状态。只有在活动状态下才需要执行关闭操作。
    • this.closed.compareAndSet(false, true): 这是一个原子操作 (CAS),它将 closed 状态从 false 设置为 true。这个操作是线程安全的关键,它确保多个线程同时调用 close() 时,只有其中一个线程能成功进入关闭流程,后续的调用将直接返回,避免重复关闭。
  2. 日志记录

    if (logger.isDebugEnabled()) {logger.debug("Closing " + this);
    }
    
    • 如果启用了 Debug 日志,则记录一条上下文正在关闭的日志。
  3. LiveBeansView 注销 (非原生镜像环境)

    if (!NativeDetector.inNativeImage()) {LiveBeansView.unregisterApplicationContext(this);
    }
    
    • LiveBeansView 是 Spring 的一个工具类,用于提供当前应用中所有活动 Bean 的实时视图(常用于 IDE 工具集成)。
    • NativeDetector.inNativeImage() 用于判断应用是否运行在 GraalVM 原生镜像中。在原生镜像中,此功能不被支持,因此跳过注销。
  4. 发布 ContextClosedEvent 事件

    try {publishEvent(new ContextClosedEvent(this));
    }
    catch (Throwable ex) {logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
    }
    
    • 重要步骤:发布一个 ContextClosedEvent 事件。
    • 所有实现了 ApplicationListener<ContextClosedEvent> 接口的 Bean 都会收到这个通知。
    • 这为其他组件在容器正式销毁前执行一些清理操作提供了机会(例如,关闭线程池、断开网络连接、持久化未保存的数据等)。
    • 异常被捕获并记录为警告,不会中断关闭流程。
  5. 停止 Lifecycle 组件

    if (this.lifecycleProcessor != null) {try {this.lifecycleProcessor.onClose();}catch (Throwable ex) {logger.warn("Exception thrown from LifecycleProcessor on context close", ex);}
    }
    
    • 重要步骤:获取 LifecycleProcessor 并调用其 onClose() 方法。
    • LifecycleProcessor 会通知所有实现了 Lifecycle 接口的 Bean(例如,Tomcat 嵌入式服务器、Netty 服务器等),让它们执行标准的停止流程。
    • 这一步是为了避免在后续单个 Bean 销毁过程中出现延迟,优先停止那些需要优雅关闭的组件(如 Web 服务器)。
    • 异常同样被捕获并记录,不会中断流程。
  6. 销毁所有单例 Bean

    destroyBeans();
    
    • 核心步骤:调用 DefaultListableBeanFactorydestroySingletons() 方法。
    • 这是资源销毁的主体部分。它会:
      • 遍历所有已创建的单例 Bean。
      • 如果 Bean 实现了 DisposableBean 接口,则调用其 destroy() 方法。
      • 如果 Bean 在定义时指定了自定义的销毁方法(如 @Bean(destroyMethod="...") 或 XML 中的 destroy-method),则调用该方法。
    • 常见的清理工作在此发生,如:数据库连接池的关闭 (DataSource)、Hibernate SessionFactory 的关闭、缓存管理器的关闭等。
  7. 关闭底层 BeanFactory

    closeBeanFactory();
    
    • 该方法通常将 this.beanFactory 设置为 null,表示这个 BeanFactory 已不可用。
    • 对于可刷新的应用上下文(如 GenericApplicationContext),它还会重置序列化 ID,为下一次 refresh() 做准备。
  8. 调用子类钩子方法

    onClose();
    
    • 这是一个空的模板方法 (protected void onClose() {}),专为子类设计。
    • 子类(如 ServletWebServerApplicationContext)可以重写此方法,在上下文完全关闭之前添加一些特定的清理逻辑。
  9. 重置应用监听器列表

    if (this.earlyApplicationListeners != null) {this.applicationListeners.clear();this.applicationListeners.addAll(this.earlyApplicationListeners);
    }
    
    • applicationListeners 列表重置回最初在 refresh() 之前的状态(即 earlyApplicationListeners)。
    • 这确保了如果上下文之后被再次刷新(refresh()),监听器列表处于一个干净、正确的初始状态。
  10. 状态置为未激活

    this.active.set(false);
    
    • 最后,将 active 状态标志设置为 false。此时,整个上下文的关闭流程正式完成。

总结:Spring Boot 2.7 资源销毁执行顺序

在 Spring Boot 2.7 应用中,当应用上下文关闭时(例如,由于 JVM 关闭或调用 SpringApplication.exit()),资源销毁的严格先后顺序如下:

  1. 发布关闭事件

    • 首先发布 ContextClosedEvent。允许应用程序监听器 (ApplicationListener) 最先感知到关闭事件并做出响应。
  2. 停止 Lifecycle 组件

    • 调用所有 Lifecycle Bean 的 stop() 方法。这是停止嵌入式 Web 服务器(如 Tomcat、Netty)的关键时刻。服务器停止接收新请求,并开始优雅关闭现有连接。
  3. 销毁单例 Beans

    • 按依赖关系反向销毁所有单例 Bean(依赖方先于被依赖方销毁)。
    • 执行每个 Bean 的销毁方法:
      • @PreDestroy 注解的方法。
      • DisposableBean 接口的 destroy() 方法。
      • @Bean(destroyMethod="...") 中指定的自定义方法(例如,DataSourceclose 方法通常在这里被调用以关闭连接池)。
  4. 框架级清理

    • 执行特定于上下文类型的最终清理(通过 onClose() 钩子)。
    • 重置内部状态(如监听器列表)。

核心要点:

  • 事件驱动:自定义清理逻辑应优先考虑实现 ApplicationListener<ContextClosedEvent>,以便最早执行。
  • 优雅关闭:Web 服务器等需要优雅关闭的组件通过在 Lifecycle 阶段停止。
  • 资源释放:数据库连接、线程池等资源的释放主要在 Bean 销毁阶段通过各自的 destroy 方法完成。
  • 顺序保障:这个顺序确保了依赖项(如数据库连接池)在其使用者(如 DAO Bean)之后才被销毁,并且像 Web 服务器这样的基础设施最早开始停止,最大限度地保证了关闭过程的平稳和无残留。
http://www.xdnf.cn/news/1448911.html

相关文章:

  • mysqldump导出远程的数据库表(在java代码中实现)
  • VUE的模版渲染过程
  • FFMPEG H264
  • OpenLayers常用控件 -- 章节一:地图缩放控件详解教程
  • 如何通过level2千档盘口分析挂单意图
  • JavaScript的输出语句
  • 三阶Bezier曲线,已知曲线上一点到曲线起点的距离为L,计算这个点的参数u的方法
  • 专题四_前缀和_一维前缀和
  • 【OC】属性关键字
  • vtk资料整理
  • Linux arm64 PTE contiguous bit
  • linux可以直接用指针操作物理地址吗?
  • torch学习 自用
  • python类的内置属性
  • AI重塑SaaS:从被动工具到智能角色的技术演进路径
  • 【面试题】OOV(未登录词)问题如何解决?
  • Leetcode_202.快乐数_三种方法解决(普通方法解决,哈希表解决,循环链表的性质解决_快慢指针)
  • 简述:普瑞时空数据建库软件(国土变更建库)之一(变更预检查部分规则)
  • PyTorch 中训练语言模型过程
  • 利用 Java 爬虫获取淘宝商品详情 API 接口
  • 嵌入式学习day41-硬件(2)
  • ansible总结2
  • 代码随想录算法训练营第一天 | 704.二分查找 27. 移除元素 977.有序数组的平方
  • python中`__annotations__` 和 `inspect` 模块区别??
  • 两个子进程之间使用命名pipe
  • 从月薪5K到年薪60W!API自动化测试如何让你突破职业瓶颈
  • K8S 部署 NFS Dynamic Provisioning(动态存储供应)
  • 【STM32】STM32F103系列USB大坑 二
  • 具身智能让人形机器人 “活” 起来:懂语言、能感知、会行动,智能进化再提速
  • 使用langgraph创建工作流系列4:人机回环