在Eclipse中终止Spring Boot应用程序 - 未调用关闭挂钩

12
我有一个Spring Boot + Spring Data Redis/KeyValue项目。我设置了一个Spring配置文件,以便在启动应用程序时嵌入所有依赖项。因此,在启动时,我启动了一个嵌入式Redis服务器。当我在Eclipse中启动它时,一切都很正常,但我希望在停止Spring Boot应用程序时停止Redis服务器。因此,我设置了几个关闭挂钩(shutdown hooks),但是它们在我从Eclipse终止应用程序时没有被调用。
SO上有类似的问题,我创建了这个问题,希望有一个Redis解决方案。此外,这些类似的问题都不是特定于Spring Boot的。
我尝试了许多方法:
  • Spring Boot的ExitCodeGenerator;
  • DisposableBean;
  • @PreDestroy;
  • 我尝试了ShutdownHook(在mp911de的回答之后)
没有一个被调用。 也许有一个Redis选项,例如超时、保持连接等等,是我不知道的一些在框外的东西吗?如何确保Redis服务器在我的Spring Boot应用程序突然停止时停止工作?

=> 我看到了这篇文章 Embedded Redis for spring boot,但是当应用程序意外终止时,@PreDestroy 并没有被调用。

以下是一些类似的问题:

我还在 eclipse.org 上看到了这篇文章,讨论了当从 eclipse 停止应用程序时,关闭挂钩未被调用的情况:Graceful shutdown of Java Applications


以下是我所有相关的代码:

启动嵌入式Redis服务器的组件(包括我的停止尝试):

@Component
public class EmbeddedRedis implements ExitCodeGenerator, DisposableBean{

    @Value("${spring.redis.port}")
    private int redisPort;

    private RedisServer redisServer;

    @PostConstruct
    public void startRedis() throws IOException {
        redisServer = new RedisServer(redisPort);
        redisServer.stop();
        redisServer.start();
    }

    @PreDestroy
    public void stopRedis() {
        redisServer.stop();
    }

    @Override
    public int getExitCode() {
        redisServer.stop();
        return 0;
    }

    @Override
    public void destroy() throws Exception {
        redisServer.stop();
    }
}

application.properties:

spring.redis.port=6379

Spring Boot 应用程序:

@SpringBootApplication
@EnableRedisRepositories
public class Launcher {

    public static void main(String[] args){
        new SpringApplicationBuilder() //
        .sources(Launcher.class)//
        .run(args);
    }

    @Bean
    public RedisTemplate<String, Model> redisTemplate() {
        RedisTemplate<String, Model> redisTemplate = new RedisTemplate<String, Model>();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        return redisTemplate;
    }


    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.setHostName("localhost");
        return jedisConnectionFactory;
    }
}

Redis服务器(~嵌入式)正在运行:

$ ps -aef | grep redis
 ... /var/folders/qg/../T/1472402658070-0/redis-server-2.8.19.app *:6379  

嵌入式Redis Maven依赖项:

<dependency>
    <groupId>com.github.kstyrc</groupId>
    <artifactId>embedded-redis</artifactId>
    <version>0.6</version>
</dependency>
5个回答

22
当使用SpringBoot和Eclipse时,您可以安装STS(Spring Tool Suite)eclipse插件以实现优雅的关闭。
安装后,将应用程序作为“Spring Boot App”而不是常规的“Java应用程序”(运行/调试配置)运行。
确保选中“启用生命周期管理”复选框,并在单击红色方形按钮停止应用程序时,它将执行优雅的关闭而不是强制关闭。
编辑:
值得注意的是,有两个“红色方形按钮”。一个在“启动”工具栏中,另一个在“控制台”面板中。启动工具栏中的一个仍然执行强制关闭,但控制台中的一个允许Spring-Boot应用程序(使用STS启动)进行优雅的关闭。

1
从STS 4.5.1开始,启动工具栏中的“红色方形按钮”似乎也会执行优雅关闭,以及“重新启动”按钮:在这两种情况下,我都会收到“请求应用程序关闭”的日志输出,但在执行强制终止时不会显示。相反,现在我找不到一种从IDE进行强制终止的方法,有时它仍然是有用的。 - Mauro Molinari

7
经过调查,发现 Eclipse 简单地终止应用程序,没有机会运行关机挂钩。 我通过 Spring-Boot 的连线找到了一个解决方法/黑客。 当执行主要方法时,在另一个线程中启动了 tomcat,并且主方法继续执行直到完成。这使我能够在按“Enter”时插入一个关闭逻辑
应用程序正常启动,控制台简单地等待输入一个 enter 键来执行 System.exit(1);
@SpringBootApplication
@EnableRedisRepositories
public class Launcher {
    public static void main(String[] args){
        new SpringApplicationBuilder() //
        .sources(Launcher.class)//
        .run(args);

        System.out.println("Press 'Enter' to terminate");
        new Scanner(System.in).nextLine();
        System.out.println("Exiting");
        System.exit(1);
    }
}

在Eclipse中启动应用程序时,我现在在控制台中按“enter”键来终止应用程序,而不是从界面上终止应用程序。

现在,关闭挂钩(@PreDestroy)被触发,Redis服务器停止运行!

虽然不是我想要的,但暂时Embedded Redis Server随应用程序一起停止,我不必手动关闭它。


您还可以将此代码拆分为CommandLineRunner或ApplicationRunner bean。@Bean public ApplicationRunner systemExitListener() { return args -> { System.out.println("按Enter键退出应用程序"); new Scanner(System.in).nextLine(); System.out.println("正在退出"); System.exit(1); }; } - Ben Ingle

3

我曾经遇到过类似的问题,虽然没有涉及 Redis。但我希望能够实现最大程度的可移植性,这样其他开发人员就不必添加STS了(如果他们不想要的话)。您可以将以下内容添加到任何Spring Boot应用程序中,以提供清理关闭。以下是我的做法,对 OP 的答案进行详细说明。

将以下内容添加到您的主类或任何 @Configuration 类:

    @Bean
    public ApplicationRunner systemExitListener() {
        return args -> {
            if (args.getOptionValues("exitListener") != null) {
                System.out.println("Press Enter to exit application");
                new Scanner(System.in).nextLine();
                System.out.println("Exiting");
                System.exit(0);
            }
        };
    }

然后在Eclipse(或您选择的IDE,甚至是命令行)中,添加参数--exitListener以激活代码。在Eclipse中,这将在运行配置中的Arguments选项卡中的Program Arguments框中设置。


3

ExitCodeGenerator要求应用程序调用exit方法

System.exit(SpringApplication
             .exit(SpringApplication.run(SampleBatchApplication.class, args)));

您可以额外注册一个“shutdown hook”(关闭挂钩)。当虚拟机正常终止(System.exit)时,挂钩将被调用。还会响应中断(Ctrl+CSIGINT)或信号(SIGHUPSIGTERM)。在某些情况下,如果虚拟机无法干净地关闭(SIGKILL,内部VM错误,本地方法中的错误),则不能保证是否调用关闭挂钩。您的EmbeddedRedis组件的代码可能如下所示:
@PostConstruct
public void startRedis() throws IOException {

    redisServer = new RedisServer(redisPort);
    redisServer.start();

    Runtime.getRuntime().addShutdownHook(new Thread(){

        @Override
        public void run() {
            redisServer.stop();
        }
    });
}

Spring也使用关闭钩子来确保上下文关闭,但令我困惑的是@PreDestroy没有被调用。 - mp911de
我刚意识到我混淆了两种行为,一种来自命令行,另一种来自IDE。我真正面临的问题来自IDE。我会更新问题。PreDestroy和DisposableBean的destroy在命令行中可以工作。 - alexbt
1
我更新了问题,很抱歉让你产生了误导,但你确实让我意识到我需要为ExitCodeGenerator调用System.exit :) 我赞同你的答案。 - alexbt

0
为解决停止Maven构建时关闭Spring Boot应用程序并启用调试,请按以下步骤操作: Maven Build -> JRE -> VM参数
添加-Dspring-boot.run.fork=false

这并不能帮助在Eclipse中终止Spring Boot应用程序。 - miloxe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接