Spring Boot 2.7 中资源销毁的先后顺序
详细解析 AbstractApplicationContext
类中的 doClose()
方法,并总结在 Spring Boot 2.7 中资源销毁的先后顺序。
方法详解
doClose()
方法是 Spring 应用上下文关闭过程的核心方法。它被设计为 protected
,意味着它主要供 Spring 框架内部调用(例如,由 close()
方法调用),但子类也可以重写它(通常通过 onClose()
钩子方法)以添加自定义的关闭逻辑。
方法执行流程如下:
-
状态检查与原子性关闭确认
if (this.active.get() && this.closed.compareAndSet(false, true)) {
this.active.get()
: 检查上下文当前是否处于活动状态。只有在活动状态下才需要执行关闭操作。this.closed.compareAndSet(false, true)
: 这是一个原子操作 (CAS),它将closed
状态从false
设置为true
。这个操作是线程安全的关键,它确保多个线程同时调用close()
时,只有其中一个线程能成功进入关闭流程,后续的调用将直接返回,避免重复关闭。
-
日志记录
if (logger.isDebugEnabled()) {logger.debug("Closing " + this); }
- 如果启用了 Debug 日志,则记录一条上下文正在关闭的日志。
-
LiveBeansView 注销 (非原生镜像环境)
if (!NativeDetector.inNativeImage()) {LiveBeansView.unregisterApplicationContext(this); }
LiveBeansView
是 Spring 的一个工具类,用于提供当前应用中所有活动 Bean 的实时视图(常用于 IDE 工具集成)。NativeDetector.inNativeImage()
用于判断应用是否运行在 GraalVM 原生镜像中。在原生镜像中,此功能不被支持,因此跳过注销。
-
发布 ContextClosedEvent 事件
try {publishEvent(new ContextClosedEvent(this)); } catch (Throwable ex) {logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); }
- 重要步骤:发布一个
ContextClosedEvent
事件。 - 所有实现了
ApplicationListener<ContextClosedEvent>
接口的 Bean 都会收到这个通知。 - 这为其他组件在容器正式销毁前执行一些清理操作提供了机会(例如,关闭线程池、断开网络连接、持久化未保存的数据等)。
- 异常被捕获并记录为警告,不会中断关闭流程。
- 重要步骤:发布一个
-
停止 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 服务器)。
- 异常同样被捕获并记录,不会中断流程。
- 重要步骤:获取
-
销毁所有单例 Bean
destroyBeans();
- 核心步骤:调用
DefaultListableBeanFactory
的destroySingletons()
方法。 - 这是资源销毁的主体部分。它会:
- 遍历所有已创建的单例 Bean。
- 如果 Bean 实现了
DisposableBean
接口,则调用其destroy()
方法。 - 如果 Bean 在定义时指定了自定义的销毁方法(如
@Bean(destroyMethod="...")
或 XML 中的destroy-method
),则调用该方法。
- 常见的清理工作在此发生,如:数据库连接池的关闭 (
DataSource
)、Hibernate SessionFactory 的关闭、缓存管理器的关闭等。
- 核心步骤:调用
-
关闭底层 BeanFactory
closeBeanFactory();
- 该方法通常将
this.beanFactory
设置为null
,表示这个 BeanFactory 已不可用。 - 对于可刷新的应用上下文(如
GenericApplicationContext
),它还会重置序列化 ID,为下一次refresh()
做准备。
- 该方法通常将
-
调用子类钩子方法
onClose();
- 这是一个空的模板方法 (
protected void onClose() {}
),专为子类设计。 - 子类(如
ServletWebServerApplicationContext
)可以重写此方法,在上下文完全关闭之前添加一些特定的清理逻辑。
- 这是一个空的模板方法 (
-
重置应用监听器列表
if (this.earlyApplicationListeners != null) {this.applicationListeners.clear();this.applicationListeners.addAll(this.earlyApplicationListeners); }
- 将
applicationListeners
列表重置回最初在refresh()
之前的状态(即earlyApplicationListeners
)。 - 这确保了如果上下文之后被再次刷新(
refresh()
),监听器列表处于一个干净、正确的初始状态。
- 将
-
状态置为未激活
this.active.set(false);
- 最后,将
active
状态标志设置为false
。此时,整个上下文的关闭流程正式完成。
- 最后,将
总结:Spring Boot 2.7 资源销毁执行顺序
在 Spring Boot 2.7 应用中,当应用上下文关闭时(例如,由于 JVM 关闭或调用 SpringApplication.exit()
),资源销毁的严格先后顺序如下:
-
发布关闭事件
- 首先发布
ContextClosedEvent
。允许应用程序监听器 (ApplicationListener
) 最先感知到关闭事件并做出响应。
- 首先发布
-
停止 Lifecycle 组件
- 调用所有
Lifecycle
Bean 的stop()
方法。这是停止嵌入式 Web 服务器(如 Tomcat、Netty)的关键时刻。服务器停止接收新请求,并开始优雅关闭现有连接。
- 调用所有
-
销毁单例 Beans
- 按依赖关系反向销毁所有单例 Bean(依赖方先于被依赖方销毁)。
- 执行每个 Bean 的销毁方法:
@PreDestroy
注解的方法。DisposableBean
接口的destroy()
方法。@Bean(destroyMethod="...")
中指定的自定义方法(例如,DataSource
的close
方法通常在这里被调用以关闭连接池)。
-
框架级清理
- 执行特定于上下文类型的最终清理(通过
onClose()
钩子)。 - 重置内部状态(如监听器列表)。
- 执行特定于上下文类型的最终清理(通过
核心要点:
- 事件驱动:自定义清理逻辑应优先考虑实现
ApplicationListener<ContextClosedEvent>
,以便最早执行。 - 优雅关闭:Web 服务器等需要优雅关闭的组件通过在
Lifecycle
阶段停止。 - 资源释放:数据库连接、线程池等资源的释放主要在 Bean 销毁阶段通过各自的
destroy
方法完成。 - 顺序保障:这个顺序确保了依赖项(如数据库连接池)在其使用者(如 DAO Bean)之后才被销毁,并且像 Web 服务器这样的基础设施最早开始停止,最大限度地保证了关闭过程的平稳和无残留。