1. 概述
在实际项目中,我们常常需要在应用关闭时执行一些清理工作,比如释放数据库连接、关闭线程池、保存缓存状态等。Spring 提供了多种方式来实现优雅停机(Graceful Shutdown),即在 JVM 退出前执行自定义的清理逻辑。
本文将系统性地介绍 Spring 中几种主流的 shutdown callback 实现方式,帮助你在实际项目中避免资源泄漏、数据丢失等“踩坑”问题。
2. Spring 中的停机回调方式
Spring 支持两种粒度的停机回调:
- ✅ 组件级(Bean-level):针对单个 Bean 的销毁逻辑
- ✅ 上下文级(Context-level):在整个 Spring 容器关闭时触发
常用的实现方式有以下几种:
@PreDestroy
注解DisposableBean
接口- 自定义 destroy 方法
- 全局
ServletContextListener
下面我们逐一演示每种方式的使用场景和注意事项。
2.1 使用 @PreDestroy
注解
这是最简单粗暴的方式,适用于大多数场景。只需要在 Bean 的某个方法上加上 @PreDestroy
,Spring 会在容器关闭时自动调用它。
@Component
public class Bean1 {
@PreDestroy
public void destroy() {
System.out.println("Callback triggered - @PreDestroy.");
}
}
⚠️ 注意:
- 该注解来自
javax.annotation.PreDestroy
,需确保引入了jakarta.annotation-api
或等效依赖。 - 如果你在 Spring Boot 项目中使用 Java 11+,可能需要显式添加依赖,否则注解不生效。
✅ 优点:简洁、直观、无需实现接口。
❌ 缺点:与 JSR-250 标准绑定,有一定侵入性。
2.2 实现 DisposableBean
接口
这是 Spring 原生提供的接口,专门用于定义销毁逻辑。
@Component
public class Bean2 implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("Callback triggered - DisposableBean.");
}
}
⚠️ 注意:
destroy()
方法签名必须抛出Exception
,这是接口强制要求。- 适合已有类需要扩展销毁逻辑的场景。
✅ 优点:Spring 原生支持,类型安全。
❌ 缺点:强依赖 Spring API,不利于解耦。
2.3 配置自定义 Bean 销毁方法
这种方式将销毁方法与配置分离,适合第三方类或无法修改源码的情况。
先定义一个普通类:
public class Bean3 {
public void destroy() {
System.out.println("Callback triggered - bean destroy method.");
}
}
然后在配置类中注册 Bean 并指定销毁方法:
@Configuration
public class ShutdownHookConfiguration {
@Bean(destroyMethod = "destroy")
public Bean3 initializeBean3() {
return new Bean3();
}
}
你也可以使用 XML 配置(虽然现在很少见了):
<bean class="com.baeldung.shutdownhooks.config.Bean3"
destroy-method="destroy">
</bean>
✅ 优点:非侵入式,适合集成外部库。
💡 小技巧:如果方法名为 close
或 shutdown
,Spring 会尝试自动推断,但显式声明更安全。
2.4 使用全局 ServletContextListener
前面三种都是 Bean 级别的回调,而 ServletContextListener
是上下文级别的监听器,适用于 Web 应用,在整个应用上下文销毁时触发。
public class ExampleServletContextListener
implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent event) {
System.out.println("Callback triggered - ContextListener.");
}
@Override
public void contextInitialized(ServletContextEvent event) {
// 应用启动时触发
}
}
需要在配置类中注册该监听器:
@Bean
ServletListenerRegistrationBean<ServletContextListener> servletListener() {
ServletListenerRegistrationBean<ServletContextListener> srb
= new ServletListenerRegistrationBean<>();
srb.setListener(new ExampleServletContextListener());
return srb;
}
⚠️ 注意:
- 仅适用于基于 Servlet 的 Web 应用(如 Spring MVC)。
- 在 Spring Boot 中,推荐使用
ApplicationListener<ContextClosedEvent>
替代,更加通用。
✅ 适用场景:全局资源清理、日志归档、监控上报等跨组件操作。
3. 总结
方式 | 粒度 | 适用场景 | 推荐指数 |
---|---|---|---|
@PreDestroy |
Bean级 | 普通组件清理 | ⭐⭐⭐⭐☆ |
DisposableBean |
Bean级 | Spring 内部组件 | ⭐⭐⭐☆☆ |
自定义 destroy 方法 | Bean级 | 第三方类集成 | ⭐⭐⭐⭐☆ |
ServletContextListener |
Context级 | Web 应用全局清理 | ⭐⭐☆☆☆ |
📌 最佳实践建议:
- 日常开发优先使用
@PreDestroy
,简单明了; - 集成外部库时使用
destroyMethod
配置; - Web 项目如需全局监听,建议使用 Spring 事件机制替代
ServletContextListener
; - 所有清理逻辑应尽量快速、幂等、无阻塞,避免拖慢停机流程。
示例代码已整理至 GitHub:https://github.com/example/spring-shutdown-callbacks
(注:原链接为示例,实际项目可自行 Fork)