如何关闭Spring Boot命令行应用程序

56

我正在使用Spring Boot构建一个命令行Java应用程序,以快速使其工作。

该应用程序加载不同类型的文件(例如CSV文件)并将它们加载到Cassandra数据库中。它不使用任何Web组件,也不是Web应用程序。

我遇到的问题是在完成任务后停止应用程序。我正在使用Spring CommandLineRunner接口和@Component运行任务,如下所示,但当任务完成后,应用程序不会停止运行,而且我找不到停止应用程序的方法。

@Component
public class OneTimeRunner implements CommandLineRunner {

    @Autowired
    private CassandraOperations cassandra;

    @Autowired
    private ConfigurableApplicationContext context;

    @Override
    public void run(String... args) throws Exception {
        // do some work here and then quit
        context.close();
    }
}

更新: 问题似乎出在spring-cassandra,因为项目中没有其他问题。有人知道为什么它会在后台保持线程运行,从而阻止应用程序停止吗?

更新: 通过升级到最新的Spring Boot版本,问题已消失。


有什么解决办法吗?这种情况发生在普通的Spring Boot(不带Cassandra)中。 - ACV
1
@ACV 不,我只是忍受它。我了解到一些Spring模块会生成自己的线程,例如Cassandra有一个连接线程池,或者当您使用监听器容器时,RabbitMQ也会生成线程。在调用context.close()停止应用程序后,这些线程需要一些时间来关闭。 - ESala
8个回答

54

我找到了一个解决方案。你可以使用这个:

public static void main(String[] args) {
    SpringApplication.run(RsscollectorApplication.class, args).close();
    System.out.println("done");
}

只需在运行时使用.close()即可。


好的,如果你看一下问题中的代码,我正在做这件事。SpringApplication.run(Main.class, args) 返回一个 ConfigurableApplicationContext,你可以将其存储或稍后使用 @Autowire。我在这个问题上遇到的问题是,在调用上下文的 close() 方法之后,应用程序会停顿一段时间。 - ESala
好的,清楚了。可能是文件需要一些时间才能刷新到数据库中。 - ACV

29
答案取决于仍在工作的内容。您可以使用线程转储(例如使用jstack)来找出。但如果它是由Spring启动的任何内容,您应该能够在主要方法中(或在CommandLineRunner中)使用ConfigurableApplicationContext.close()停止应用程序。

好的,我使用断点调试了应用程序,发现有17个线程:Thread[New I/O worker#(1-17)]。我需要进一步调查一下。关于ConfigurableApplicationContext.close(),关闭应用程序需要大约30秒的时间,这正常吗? - ESala
它将不得不等待正在工作的内容释放线程。对我来说听起来像是超时。 - Dave Syer
确切地说,我怀疑Spring Cassandra出于某种原因正在保持这些线程运行。 - ESala
1
使用ConfigurableApplicationContext.close()关闭应用程序是符合当前标准的良好关闭方式吗? - KCK
不确定您的意思。这是以编程方式关闭应用程序上下文的唯一方法。 - Dave Syer

28

这是 @EliuX 的答案与 @Quan Vo 的答案的结合。感谢你们两个!

主要区别在于,我将 SpringApplication.exit(context) 的响应代码作为参数传递给 System.exit(),因此如果关闭 Spring 上下文时出现错误,您会注意到。

SpringApplication.exit() 将关闭 Spring 上下文。

System.exit() 将关闭应用程序。

@Component
public class OneTimeRunner implements CommandLineRunner {

    @Autowired
    private ConfigurableApplicationContext context;

    @Override
    public void run(String... args) throws Exception { 
       System.exit(SpringApplication.exit(context));
    }
}

6

我在我的当前项目中(spring boot应用程序)也遇到了这个问题。

我的解决方案是:

// releasing all resources
((ConfigurableApplicationContext) ctx).close();
// Close application
System.exit(0);

context.close() 不会停止我们的控制台应用程序,它只是释放资源。


4

请使用org.springframework.boot.SpringApplication#exit。例如:

@Component
public class OneTimeRunner implements CommandLineRunner {

    @Autowired
    private ConfigurableApplicationContext context;

    @Override
    public void run(String... args) throws Exception { 
        SpringApplication.exit(context);
    }
}

1
一个命令行应用程序在关闭时应该返回一个退出代码。本文介绍了如何使用ExitCodeGenerator以及如何在Spring应用程序中使用它。这种方法对我很有效:
@SpringBoot应用 public class ExitCodeGeneratorDemoApplication implements ExitCodeGenerator {
public static void main(String[] args) {
    System.exit(SpringApplication
      .exit(SpringApplication.run(DemoApplication.class, args)));
}

@Override
public int getExitCode() {
    return 42;
}

}

https://www.baeldung.com/spring-boot-exit-codes


1

我开发了一个命令行Spring应用程序(Spring版本2.7),在执行业务逻辑后停止。我所需要的只是以下应用程序配置,告诉Spring这不是Web应用程序。

配置

spring:
  main:
    web-application-type: none

Spring主类

@SpringBootApplication
public class DataInjectionApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(DataInjectionApplication.class, args);
    }


    @Override
    public void run(String... args) {
        
        //execute business logic here

    }
}

0

当没有挂起的线程时,应用程序会自动停止而不需要使用exit()。我曾经遇到过同样的问题,最终找到了它发生的原因。我一直依赖于Spring机制,在应用程序退出时关闭所有的Closeable bean,但它是在JVM关闭钩子上调用的,因此除非有一些非守护线程正在运行,否则它将永远不会被调用。我通过在CommandLineRunner中手动关闭所有具有打开线程的bean来解决了这个问题。不幸的是,对于所有由自动配置创建的bean(例如spring-data-elasticsearch创建此类bean),这也是必需的。


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