Gradle构建Spring Boot/Batch应用Linux部署Shutdown Hook不执行问题求助
我之前也碰到过一模一样的问题——在IDE里跑Spring Boot应用时Shutdown Hook好好的,一用Gradle application插件打包部署到Linux就失效,折腾了好一阵才找到根源,给你分享几个可行的解决方案:
先搞清楚为啥会失效
Gradle的application插件生成的bash启动/停止脚本,默认的停止逻辑有点“粗暴”:当你执行./your-app stop或者进程收到终止信号时,脚本会先尝试发送SIGTERM,但如果几秒后进程还没退出,就会直接发送SIGKILL(也就是kill -9)。而SIGKILL是强制终止,JVM根本没机会执行任何Shutdown Hook。另外,如果你的Shutdown Hook是手动通过Runtime.getRuntime().addShutdownHook()注册的,有时候会和Spring的上下文生命周期脱节,导致在脚本环境下没被触发。
解决方案1:用Spring原生的生命周期回调代替手动Hook
与其自己注册Shutdown Hook,不如利用Spring的生命周期管理机制,这样更稳定也更贴合Spring生态。有两种常用方式:
方式A:实现DisposableBean接口
创建一个@Component类,实现DisposableBean的destroy()方法,Spring在关闭上下文时会自动调用它:
@Component public class AppShutdownHandler implements DisposableBean { @Override public void destroy() throws Exception { // 这里写你要执行的关闭逻辑:比如终止Spring Batch任务、清理资源等 System.out.println("执行Spring上下文关闭前的自定义操作..."); // 如果需要关闭Spring上下文,其实Spring会自动处理,这里只需要加你的业务逻辑 } }
方式B:使用@PreDestroy注解
更简洁的方式,直接在Bean的方法上标注@PreDestroy:
@Component public class AppShutdownHandler { @PreDestroy public void onAppShutdown() { // 执行关闭逻辑 System.out.println("通过@PreDestroy执行关闭操作..."); } }
解决方案2:修改Gradle启动脚本的停止逻辑
如果一定要用手动注册的Shutdown Hook,那得调整Gradle生成的停止脚本,让它别动不动就发SIGKILL。在你的build.gradle里添加如下配置:
plugins { id 'java' id 'org.springframework.boot' version '2.7.0' // 替换成你的Spring Boot版本 id 'application' } application { mainClass.set("com.yourpackage.YourApplication") // 你的主类全路径 // 修改启动脚本的停止逻辑 startScripts { // 自定义停止脚本的内容 stopScript { doLast { def stopScriptFile = file(outputDir, "${applicationName}.stop") def scriptContent = stopScriptFile.text // 把脚本里的kill -9替换成普通kill(默认发SIGTERM) scriptContent = scriptContent.replaceAll('kill -9 "\\$PID"', 'kill "\\$PID"') // 也可以去掉超时后强制kill的逻辑,根据你的需求调整 scriptContent = scriptContent.replace(/if \[ "\$?" -ne 0 \]; then\n.*?fi/s, '') stopScriptFile.text = scriptContent } } } }
配置完成后重新执行./gradlew distTar,生成的压缩包里的停止脚本就会用更温和的方式终止进程,给JVM足够时间执行Shutdown Hook。
最后注意测试方式
千万不要用kill -9 <pid>来测试!这个命令会直接干掉进程,任何Shutdown Hook都跑不了。正确的测试方式是:
- 用
./your-app start启动应用 - 用
./your-app stop停止,或者用kill <pid>(不带-9)发送终止信号
内容的提问来源于stack exchange,提问作者Sanjeev

