spring中销毁方法需在bean不可用前可靠执行,按@predestroy→disposablebean.destroy()→destroy-method顺序触发;单例bean容器关闭时逆序销毁,prototype需显式管理;资源释放须分类型处理并规避依赖、阻塞等陷阱。

在 Spring 中,销毁方法是释放系统底层资源(如数据库连接、线程池、文件句柄、网络套接字、本地缓存等)的关键入口。关键不在于“能不能做”,而在于“什么时候做、怎么做才可靠”。核心原则是:销毁逻辑必须在 Bean 真正不可用前执行,且需兼顾异常容错与执行顺序。
明确销毁方法的触发时机和执行顺序
Spring 容器关闭时,单例 Bean 的销毁按依赖逆序进行;多实例 Bean(prototype)默认不自动管理销毁,需显式处理。销毁回调有三类,执行顺序固定:
- @PreDestroy:最早执行,属于 JSR-250 标准,适用于轻量清理(如标记状态、记录日志)
- DisposableBean.destroy():中间执行,Spring 原生接口,适合强保障型资源释放(如 close() 调用必须成功)
- destroy-method 配置的方法:最后执行,适合封装复杂逻辑或调用第三方库的 shutdown() 方法
例如一个使用 HikariCP 的 DataSource Bean,应优先在 destroy() 中调用 dataSource.close(),而非仅靠 @PreDestroy —— 因为后者若抛异常可能被静默吞掉,而 destroy() 的异常虽也被捕获记录,但其执行位置更靠近资源持有层,语义更清晰。
释放常见底层资源的典型写法
不是所有资源都支持“调一次 close 就完事”。实际中需区分资源类型并针对性处理:
-
数据库连接池(如 HikariCP、Druid):直接调用
dataSource.close()。注意避免在destroy()中重复关闭已关闭的池(加判空或 try-catch) -
线程池(如 ThreadPoolExecutor):先
shutdown()拒绝新任务,再awaitTermination()等待运行中任务结束,超时后调用shutdownNow()强制中断 -
HTTP 客户端(如 Apache HttpClient、OkHttp):调用
httpClient.close()或connectionManager.shutdown(),确保连接池和底层 socket 全部释放 -
文件/流资源(如 RandomAccessFile、MappedByteBuffer):使用
try-with-resources不适用(因生命周期由 Spring 管理),应在destroy()中显式close()或cleaner.clean()
规避销毁阶段的常见陷阱
销毁逻辑看似简单,但线上故障常源于细节疏忽:
-
不要在销毁方法中依赖其他 Bean:容器已开始逆序销毁,依赖的 Bean 可能已被销毁,调用将抛
IllegalStateException或 NPE -
避免耗时操作阻塞容器关闭:如等待 30 秒的
awaitTermination可能导致 JVM 进程 hang 住;建议设合理超时(如 5 秒),超时后强制清理 -
多实例 Bean(prototype)不会自动触发销毁回调:除非手动注册(如通过
ConfigurableBeanFactory.registerDisposableBean()),否则需业务方自行管理生命周期 -
静态资源或全局缓存需额外清理:如 Guava Cache 的
invalidateAll()、Caffeine 的cleanUp(),应在destroy()中主动触发,防止内存泄漏
推荐组合方案:注解 + 接口 + 显式关闭
现代 Spring 应用建议分层设计销毁逻辑:
- 用
@PreDestroy做前置通知(如打点日志、更新监控指标) - 用
DisposableBean做核心资源释放(保证 Spring 容器级生命周期语义) - 对无法修改源码的第三方类,用
@Bean(destroyMethod = "shutdown")指定方法,或包装一层适配器 - 关键组件(如连接池)可额外实现
AutoCloseable,Spring 会自动识别并调用close(),提供双重保障
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











