You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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上下文关闭。

  1. 添加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>
  1. 创建控制台关闭监听类:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 08:50:38