spring容器无原生“exit信号量”,但可通过smartlifecycle接口在关闭前按phase顺序执行stop()并异步清理,配合优雅停机配置(如server.shutdown=graceful)和@predestroy等销毁回调实现任务兜底,而kill-9等强杀场景下需依赖超时释放与幂等设计保障一致性。

Spring容器本身不提供“exit信号量”这一原生概念,但你可以通过标准生命周期机制,在容器真正关闭前捕获并处理尚未完成的任务——关键不是等待信号,而是主动参与 shutdown 流程,并在资源释放阶段做任务兜底。
用 SmartLifecycle 控制停机时序
SmartLifecycle 是最贴近“动态捕获残余任务”需求的接口。它允许你声明组件的启动/停止顺序和运行状态,让 Spring 在 close() 过程中按 phase 值依次调用 stop(),而非简单跳过。
- 实现 SmartLifecycle,重写 isRunning() 并用线程安全变量(如 AtomicBoolean)维护真实运行态
- 在 start() 中设 running = true;否则 stop() 永远不会触发
- 设置合理的 getPhase():若需优先停止(如切断流量入口),返回负值(如 -1000);若需延后停止(如等 MQ 消息消费完),返回大正数(如 Integer.MAX_VALUE)
- 务必重载 stop(Runnable callback):在其中异步执行清理逻辑,完成后手动调用 callback.run(),确保 Spring 等待该任务结束才继续销毁后续 Bean
配合优雅停机配置生效
没有基础配置,再完善的 Lifecycle 实现也会静默失效。
- 启用 Spring Boot 内置优雅停机:server.shutdown=graceful(2.3+ 版本)
- 为 shutdown 阶段预留足够时间:spring.lifecycle.timeout-per-shutdown-phase=30s
- 确保 Web 容器(如 Tomcat)支持 graceful shutdown —— 默认已开启,但需避免被外部进程(如 k8s preStop 脚本)误配为立即 kill
在 destroy 阶段做最终兜底
SmartLifecycle.stop() 执行后,Spring 仍会调用 Bean 的销毁回调。可在此补充不可遗漏的收尾动作。
- 使用 @PreDestroy 标注方法:适用于单例 Bean,容器 close 前自动调用
- 实现 DisposableBean 接口:覆写 destroy(),与 @PreDestroy 可共存,执行顺序不保证,建议只选其一
- 在 @Bean(destroyMethod = "xxx") 中指定自定义销毁方法:适合第三方类或无法修改源码的场景
- 所有方式都仅对 singleton 作用域 Bean 有效;prototype Bean 不参与容器销毁流程
注意强杀场景的边界
kill -9、JVM crash、物理断电等情形下,Java 层任何回调(包括 stop() 和 destroy())均无法执行。因此:
- 不要把关键事务提交、数据落盘等强一致性操作全押在 shutdown 回调里
- 对 Redis 锁、MQ 消费位点等资源,应结合超时自动释放(如 Redisson lock 的 leaseTime)、幂等设计、外部监控补偿等手段兜底
- 可通过 JVM shutdown hook 做极简日志记录,但它同样无法对抗 kill -9











