如何防止 Spring 应用程序上下文关闭,直到关闭挂钩被触发

Rus*_*nko 3 java spring asynchronous spring-boot

我有一个 spring-boot 应用程序。

我已经SmartLifecycle在我的 bean 中实现了接口,start它在它的stop方法中启动异步 snmp 服务器并在它的方法中停止它。

一切正常,除了主应用程序上下文在启动后立即停止,所以我的服务器 bean 也在启动后立即停止。

我所需要的只是让 spring 上下文仅在关闭挂钩被触发时停止。

这不是一个网络应用程序,所以我不需要spring-boot-starter-web,这是通过启动 webserver 来解决这个问题的,它可以防止上下文停止,直到 webserver 停止。

我可以在上下文开始后CountDownLatch立即使用类似的东西并等待它在我的main方法中为零。像这样:

public static void main(String[] args) throws InterruptedException {
    ConfigurableApplicationContext ctx = SpringApplication.run(SnmpTrapRetranslatorApplication.class, args);
    CountDownLatch snmpServerCloseLatch = ctx.getBean("snmpServerCloseLatch", CountDownLatch.class);
    snmpServerCloseLatch.await();
}
Run Code Online (Sandbox Code Playgroud)

我的服务器 bean 的start方法将使用 count 创建这个闩锁1,而stop方法将调用snmpServerCloseLatch.countDown().

此处描述此技术。

但是这有什么问题,我的main方法负责等待我的自定义服务器 bean 停止。我觉得这不太对。

例如如何spring-boot-starter-web做到这一点?当它启动 tomcat 时,它会一直运行直到收到关闭钩子,并且它不需要在main方法中有任何管理代码。只有当上下文接收到shoudown 信号时它才会停止。

例如,当@Scheduled我的 bean 中有方法时,也会出现相同的行为。Spring 也不会自动停止上下文。仅在CTRL-C.

我想达到类似的效果。我的main方法应该只有一行:启动上下文。上下文应在启动或停止时启动和停止我的异步服务器(已通过 实现SmartLifecycle),并且在请求关闭之前不应停止(CTRL-C、SIGINT 等)。

小智 6

    SpringApplication app = new SpringApplication(Main.class);
    app.setRegisterShutdownHook(false);
    ConfigurableApplicationContext applicationContext= app.run();
    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
        @Override
        public void run() {
            //do your things
            applicationContext.close();
        }
    }));
Run Code Online (Sandbox Code Playgroud)


Rus*_*nko 5

我的调查使我找到了问题的核心:守护线程

我使用的 snmp 服务器实现 (snmp4j) 在内部使用守护线程。所以即使当snmp服务器启动时,JVM中也没有更多的活动用户线程,所以它退出了。

TL/DR:

只需将此方法添加到任何 bean(snmp 服务器 bean 是很好的候选者):

@Scheduled(fixedDelay = 1000 * 60 * 60) // every hour
public void doNothing() {
    // Forces Spring Scheduling managing thread to start
}
Run Code Online (Sandbox Code Playgroud)

(不要忘记添加@EnableScheduling到您的弹簧配置中)。

解释:

为了防止停止 spring 上下文,当 SNMP 服务器仍在运行时,我们需要任何非守护线程在 JVM 中处于活动状态。不一定是main线程。所以我们可以让main方法完成。

  1. 我们可以从我们的服务器 bean 的start方法运行新的非守护线程。该线程将wait在一些锁定while回路检测一些running变量,而我们的stop方法将它设置running变量false,并notifyAll在此锁定。

    这样,我们的非守护进程线程将一直处于活动状态,直到触发shotdown 挂钩(并阻止JVM 退出)。关闭钩子后,spring上下文生命周期close方法会调用所有SmartLifecyclebean的close方法,导致SNMP服务器bean的stop方法调用,导致设置running为false,导致我们的非守护线程停止,让JVM优雅停止.

  2. 或者,我们可以以类似的方式使用 Spring 的调度线程。它也是非守护线程,因此会阻止JVM退出。而Spring自己管理这个线程,所以在触发shutdown hook时会自动停止。

    要启动 Spring 的调度线程,我们需要@Scheduled任何 bean 中的任何方法。


我认为第一种(手动)方法仍然更“正确”,同时需要更多的异步编码(众所周知,这很容易出错)。谁知道 Spring 将来会如何改变它的调度实现。