如何中止Spring-Boot的启动?

17

我正在编写一个Spring Boot应用程序来监视目录并处理添加到其中的文件。我在配置类中使用WatchService注册了该目录:

@Configuration
public class WatchServiceConfig {

    private static final Logger logger = LogManager.getLogger(WatchServiceConfig.class);

    @Value("${dirPath}")
    private String dirPath;

    @Bean
    public WatchService register() {
        WatchService watchService = null;

        try {
            watchService = FileSystems.getDefault().newWatchService();
            Paths.get(dirPath).register(watchService, ENTRY_CREATE);
            logger.info("Started watching \"{}\" directory ", dlsDirPath);
        } catch (IOException e) {
            logger.error("Failed to create WatchService for directory \"" + dirPath + "\"", e);
        }

        return watchService;
    }
}

如果在注册目录时失败,我希望能够优雅地中止Spring Boot启动。有人知道我该如何做吗?


你尝试过 System.exit(SIGNUM) 吗? - Turtle
我宁愿优雅地完成它。System.stop()会立即终止JVM - Zhubin Salehi
那么RuntimeException呢? - Turtle
这将创建一个名为register的bean,这对于WatchService bean来说是一个糟糕的名称。我强烈建议将方法重命名为更适合bean名称的内容,例如public WatchService watchService() { ... } - M. Justin
4个回答

13

获取应用程序上下文,例如:

@Autowired
private ConfigurableApplicationContext ctx;

如果找不到目录,请调用close方法:

ctx.close();

优雅地关闭应用程序上下文,从而关闭Spring Boot应用程序本身。

更新:

一个更详细的示例,基于问题中提供的代码。

主类

@SpringBootApplication
public class GracefulShutdownApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(GracefulShutdownApplication.class, args);
        try{
            ctx.getBean("watchService");
        }catch(NoSuchBeanDefinitionException e){
            System.out.println("No folder to watch...Shutting Down");
            ctx.close();
        }
    }

}

文件监控服务配置

@Configuration
public class WatchServiceConfig {

    @Value("${dirPath}")
    private String dirPath;

    @Conditional(FolderCondition.class)
    @Bean
    public WatchService watchService() throws IOException {
        WatchService watchService = null;
        watchService = FileSystems.getDefault().newWatchService();
        Paths.get(dirPath).register(watchService, ENTRY_CREATE);
        System.out.println("Started watching directory");
        return watchService;
    }

文件夹状态

public class FolderCondition implements Condition{

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        String folderPath = conditionContext.getEnvironment().getProperty("dirPath");
        File folder = new File(folderPath);
        return folder.exists();
    }
}

根据目录是否存在,使WatchService Bean成为@Conditional。然后在您的主类中检查WatchService Bean是否存在,如果不存在,则通过调用close()关闭应用程序上下文。


我尝试了这个,但是当调用ctx.close()时,我遇到了以下异常:java.lang.IllegalStateException: LifecycleProcessor not initialized - call 'refresh' before invoking lifecycle methods via the context: org.springframework.context.annotation.AnnotationConfigApplicationContext@385e9564: startup date [Fri Dec 02 16:45:12 EST 2016]; root of context hierarchy - Zhubin Salehi
然后我添加了 ctx.refresh(),情况变得更糟了:org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'application': Unsatisfied dependency expressed through field 'directoryMonitorService'; - Zhubin Salehi
@ZhubinSalehi 我已经添加了一个更详细的示例,显示如何以及在哪里调用close()方法。希望这有所帮助。 - Kyle Anderson
关于调用 ctx.getBean("watchService"); 的部分没有生效,因为在调用之前应用程序无法启动。但是有关创建 Condition 的第二部分完美运作。谢谢! - Zhubin Salehi
@ZhubinSalehi 这是有意为之的。只有当文件夹存在时,才会创建“watchService”bean。这是由FolderCondition.class强制执行的。然后我们检查ApplicationContext以查看是否存在“watchService”bean。如果不存在,这意味着该文件夹不存在,我们会捕获异常并优雅地关闭应用程序。希望这样说得清楚。 - Kyle Anderson

7

接受的答案是正确的,但过于复杂。不需要使用Condition,然后检查bean是否存在,然后关闭ApplicationContext。只需在WatchService创建期间检查目录是否存在,并抛出异常即可,由于无法创建bean而导致应用程序启动失败。


0

如果您对当前的IOException消息感到满意,您可以让您的bean抛出它来中止启动:

@Bean
public WatchService watchService() throws IOException {
    WatchService watchService = FileSystems.getDefault().newWatchService();
    Paths.get(dirPath).register(watchService, ENTRY_CREATE);
    logger.info("Started watching \"{}\" directory ", dlsDirPath);
}

如果您想要比默认的IOException更友好的错误消息(以更好地帮助指出错误),您可以抛出自定义异常并附上自定义的异常消息:
@Bean
public WatchService watchService() {
    try {
        WatchService watchService = FileSystems.getDefault().newWatchService();
        Paths.get(dirPath).register(watchService, ENTRY_CREATE);
        logger.info("Started watching \"{}\" directory ", dlsDirPath);
        return watchService;
    } catch (IOException e) {
        throw new IllegalStateException(
                "Failed to create WatchService for directory \"" + dirPath + "\"", e);
    }
}

-2

https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-application-exit

每个SpringApplication都会向JVM注册一个关闭挂钩,以确保ApplicationContext在退出时优雅地关闭。可以使用所有标准的Spring生命周期回调(例如DisposableBean接口或@PreDestroy注释)。
此外,如果bean希望在应用程序结束时返回特定的退出代码,则可以实现org.springframework.boot.ExitCodeGenerator接口。
您应该实现@PreDestroy方法来释放资源/文件。然后在启动期间,当检测到某些错误时,可以抛出一些RuntimeException-它开始关闭应用程序上下文。

PreDestroy 是用于Bean级别的清理,而不是用于退出应用程序。 - Abhijit Sarkar
当Spring Boot应用程序停止上下文时,将调用所有@PreDestroy bean方法。这就是我的观点。显然,当您有一些必须进行清理的类时,代码应该放在同一个类中。这样抽象就不会从您的类中泄漏出来。将文件资源清理放在@PreDestroy中是可以的。 - Michał Mielec
1
请再次阅读问题。问题不是如何在关闭时进行清理,而是如何关闭应用程序。PreDestroy与天气报告一样相关。 - Abhijit Sarkar
RuntimeException 会触发这个。我提到 @PreDestroy 是因为通常当你需要优雅地关闭时,你也需要一个地方来放置清理代码。 - Michał Mielec

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