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>

✅ 优点:非侵入式,适合集成外部库。
💡 小技巧:如果方法名为 closeshutdown,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 应用全局清理 ⭐⭐☆☆☆

📌 最佳实践建议:

  1. 日常开发优先使用 @PreDestroy,简单明了;
  2. 集成外部库时使用 destroyMethod 配置;
  3. Web 项目如需全局监听,建议使用 Spring 事件机制替代 ServletContextListener
  4. 所有清理逻辑应尽量快速、幂等、无阻塞,避免拖慢停机流程。

示例代码已整理至 GitHub:https://github.com/example/spring-shutdown-callbacks
(注:原链接为示例,实际项目可自行 Fork)


原始标题:Spring Shutdown Callbacks