Spring Boot项目点击CMD关闭按钮时如何执行destroy()方法?
问题描述
以下是实现了销毁逻辑的Spring Boot服务类:
@Service @Slf4j public class ScheduleService implements CommandLineRunner, ApplicationListener<ContextClosedEvent>, DisposableBean { @Override public void destroy() throws Exception { try { System.out.println("exit"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }
- 在IntelliJ中关闭项目时,
destroy()方法可正常执行。 - 打包为Jar包在CMD窗口运行后,点击窗口的“X”按钮强制关闭时,该方法无法执行。
- 使用Ctrl+C正常退出时无异常,但用户常点击CMD窗口的“X”按钮。
需求:实现点击CMD窗口“X”按钮强制退出时,也能执行destroy()函数。
解决方案
方法1:注册Windows关闭信号处理器
通过注册SignalHandler捕获Windows的CTRL_CLOSE_EVENT信号(对应CMD窗口点击X的操作),主动触发Spring上下文关闭,从而执行destroy()方法。
修改ScheduleService类,添加信号注册逻辑:
@Service @Slf4j public class ScheduleService implements CommandLineRunner, ApplicationListener<ContextClosedEvent>, DisposableBean { @Autowired private ApplicationContext applicationContext; @Override public void run(String... args) throws Exception { // 注册Windows关闭信号处理器 try { Signal signal = new Signal("INT"); Signal.handle(signal, sig -> { log.info("捕获到窗口关闭信号,开始执行销毁逻辑"); // 关闭Spring上下文,触发destroy方法 ((ConfigurableApplicationContext) applicationContext).close(); System.exit(0); }); } catch (Exception e) { log.error("注册信号处理器失败", e); } } @Override public void destroy() throws Exception { try { System.out.println("exit"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void onApplicationEvent(ContextClosedEvent event) { try { destroy(); } catch (Exception e) { log.error("执行销毁逻辑失败", e); } } }
注意:Signal类属于sun.misc包,主流JDK版本均支持,但属于非公开API,若需更好兼容性可选用方法2。
方法2:使用JNA捕获Windows窗口关闭事件
通过JNA调用Windows API监听控制台窗口关闭事件,触发Spring上下文关闭。
- 添加JNA依赖到
pom.xml:
<dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.13.0</version> </dependency> <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna-platform</artifactId> <version>5.13.0</version> </dependency>
- 创建控制台关闭监听类:
import com.sun.jna.Native; import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.platform.win32.WinBase; import com.sun.jna.platform.win32.WinNT; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Component public class WindowsConsoleShutdownListener { private final ConfigurableApplicationContext applicationContext; private ExecutorService executorService; public WindowsConsoleShutdownListener(ConfigurableApplicationContext applicationContext) { this.applicationContext = applicationContext; } @PostConstruct public void init() { executorService = Executors.newSingleThreadExecutor(); executorService.submit(() -> { WinNT.HANDLE consoleHandle = Kernel32.INSTANCE.GetStdHandle(WinBase.STD_INPUT_HANDLE); byte[] buffer = new byte[1]; while (true) { // 监听控制台关闭事件 if (!Kernel32.INSTANCE.ReadConsole(consoleHandle, buffer, 1, null, null)) { int errorCode = Native.getLastError(); // ERROR_OPERATION_ABORTED表示控制台窗口被关闭 if (errorCode == 995) { System.out.println("捕获到控制台窗口关闭事件"); applicationContext.close(); break; } } } }); } @PreDestroy public void destroy() { executorService.shutdownNow(); } }
当用户点击CMD窗口的X时,ReadConsole会返回错误,检测到错误码995后,关闭Spring上下文,触发destroy()方法执行。
补充说明
两种方法的核心都是捕获窗口关闭事件,主动触发Spring应用上下文的关闭,从而让DisposableBean的destroy()方法或@PreDestroy注解的方法被执行。
内容的提问来源于stack exchange,提问作者baeyeongbin
相关产品推荐
相关产品推荐

