导读:本期聚焦于江户川创作的《Spring Boot整合时如何正确使用@PreDestroy实现优雅关闭?》,敬请观看详情。容器停止时Bean的资源释放常被忽视,导致连接池泄漏或文件锁未释放。@PreDestroy注解能在Spring Boot销毁Bean前回调指定方法,配合DisposableBean或@Bean destroyMethod可覆盖不同场景。实际项目中常遇到销毁顺序不可控、异步任务中断异常等问题。理解ServletWebServer停止与Bean生命周期的关联,才能设计出可靠的优雅关闭方案,避免强制Kill造成数据不一致。

在Spring Boot应用停止运行的过程中,Bean实例的清理动作直接决定了系统能否做到优雅关闭。很多工程师知道用@PreDestroy标注方法来释放资源,但在整合Web服务器、线程池与第三方客户端时,销毁逻辑的执行时机和顺序往往和预期不同。本文从生命周期机制、整合配置方式以及常见异常三个层面,说明如何让@PreDestroy在Spring Boot项目中稳定发挥作用。

Spring Boot整合时如何正确使用@PreDestroy实现优雅关闭?

@PreDestroy在Spring Boot生命周期中的底层机制

Spring容器在刷新完成后会注册所有单例Bean,当调用AnnotationConfigApplicationContextclose方法或接收到停机信号时,容器进入销毁阶段。此时CommonAnnotationBeanPostProcessor会扫描Bean中带有@PreDestroy注解的方法,并通过反射调用它。这个过程发生在Bean实例从容器移除之前,因此方法内部依然可以安全访问Bean的字段和依赖。

需要注意的是,@PreDestroy的执行依赖于Bean的销毁顺序。在Spring Boot内嵌Tomcat的场景中,ServletWebServerApplicationContext会先关闭Web服务器,再销毁业务Bean。如果我们在Controller里用@PreDestroy去释放与请求相关的资源,就可能因为服务器已停止而抛出异常。理解这一顺序,是避免关闭阶段报错的前提。

DisposableBean接口和@Bean(destroyMethod="...")相比,@PreDestroy是JSR-250标准注解,不耦合Spring特定接口,更利于代码迁移。但当Bean由工厂方法创建且未指定销毁方法时,容器无法自动识别关闭逻辑,这时就必须显式使用@PreDestroy或配置destroyMethod。

在Spring Boot中整合@PreDestroy的三种实践方式

最常见的方式是在业务组件类上直接标注@PreDestroy方法。例如一个持有Redis连接池的服务,可以在销毁时调用close释放连接。示例代码如下,其中@PreDestroy修饰的方法不能有参数,也不能抛出受检异常,否则容器会记录警告并跳过后续清理。

import javax.annotation.PreDestroy;
import org.springframework.stereotype.Service;

@Service
public class RedisPoolService {
    private NativeRedisPool pool;

    public RedisPoolService() {
        pool = new NativeRedisPool();
    }

    public String get(String key) {
        return pool.get(key);
    }

    @PreDestroy
    public void cleanup() {
        if (pool != null) {
            pool.close();
            System.out.println("Redis连接池已释放");
        }
    }
}

第二种方式是在@Bean定义中配合destroyMethod使用,适合第三方库对象。Spring Boot默认对返回CloseableAutoCloseable的Bean自动调用close,但如果我们想执行自定义步骤,可以显式声明。下面的配置展示了如何在一个数据源Bean销毁前打印日志并释放连接。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DataSourceConfig {

    @Bean(destroyMethod = "shutdown")
    public HikariDataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test");
        return ds;
    }
}

第三种方式是实现DisposableBean接口,虽然不如@PreDestroy解耦,但在需要感知容器关闭事件的复杂组件中依然有效。实际项目中,我们常把@PreDestroy用于普通服务类,把DisposableBean用于基础架构组件,两者可以共存且执行顺序由容器统一调度。

整合过程中典型的关闭异常与排查思路

第一个典型问题是销毁方法抛出未捕获的运行时异常,导致容器关闭日志被污染且后续Bean无法释放。Spring在调用@PreDestroy时只会记录异常,不会中断整个关闭流程,但这会造成资源泄漏。建议在方法内用try-catch包裹所有释放逻辑,并输出明确错误上下文。

第二个问题是异步任务未停止。如果在@PreDestroy中只关闭了线程池却未等待任务结束,应用进程可能在任务执行中途被强制退出。正确做法是在方法里调用shutdown后配合awaitTermination,设定合理超时。下面代码演示了安全的线程池销毁。

import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

@Component
public class TaskManager {
    private final ExecutorService executor = new ThreadPoolExecutor(2, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

    @PreDestroy
    public void destroy() {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

第三个问题是Bean依赖顺序导致空指针。例如A的@PreDestroy调用了B的方法,但B已被先销毁。可通过@DependsOn或把共用资源提取到独立生命周期组件来规避。Spring Boot的优雅关闭并非仅靠一个注解,而是需要结合容器停机钩子、Web服务器停止策略以及Bean销毁逻辑共同设计。

Spring_Boot@PreDestroy优雅关闭修改时间:2026-08-19 01:20:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。