How can I abort Spring-Boot startup?
Asked Answered
V

4

18

I'm writing a Spring-Boot application to monitor a directory and process files that are being added to it. I register the directory with WatchService in a configuration class:

@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;
    }
}

I would like to abort Spring Boot startup gracefully if registering the directory fails. Does anybody know how I can do this?

Vincenty answered 2/12, 2016 at 20:55 Comment(4)
Have you tried System.exit(SIGNUM)Tillery
I'd rather do it gracefully. System.stop() kills the JVM immediately.Vincenty
Then RuntimeException?Tillery
This creates a bean named register, which is a terrible name for a WatchService bean. I'd strongly suggest renaming the method to something more appropriate for the bean name, e.g. public WatchService watchService() { ... }.Superman
P
13

Get the Application Context, e.g.:

@Autowired
private ConfigurableApplicationContext ctx;

Then call the close method if you cannot find the directory:

ctx.close();

That gracefully shutdowns the Application Context and thus the Spring Boot Application itself.

Update:

A more detailed example based on the code provided in the Question.

Main Class

@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();
        }
    }

}

WatchService Configuration

@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;
    }

Folder Condition

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();
    }
}

Make the WatchService Bean @Conditional based on whether the directory is present or not. Then in your Main Class, check whether the WatchService Bean exists, and if not shutdown the Application Context by calling close().

Pyroligneous answered 2/12, 2016 at 21:34 Comment(5)
I tried this but I get this exception when ctx.close() is called: 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 hierarchyVincenty
Then I added ctx.refresh() and things got even worse: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'application': Unsatisfied dependency expressed through field 'directoryMonitorService'; Vincenty
@ZhubinSalehi I've added a more detailed example showing exactly how and where to call the close() method. Hope that helps.Pyroligneous
The part about calling ctx.getBean("watchService"); didn't work, because the application fails to start up before the call. But the second part about creating a Condition works perfectly. Thanks!Vincenty
@ZhubinSalehi That is intentional. The 'watchService' bean only is created if the folder exists. That is enforced by FolderCondition.class Then we check the ApplicationContext to see if the 'watchService' bean exists. If it doesn't, which again means that folder doesn't exist, we catch the Exception and shutdown the application gracefully. Hope that makes sense.Pyroligneous
O
7

The accepted answer is correct, but unnecessarily complex. There's no need for a Condition, and then checking for the existence of the bean, and then closing the ApplicationContext. Simply checking for the presence of the directory during WatchService creation, and throwing an exception will abort the application startup due to failure to create the bean.

Olecranon answered 9/1, 2019 at 1:40 Comment(0)
S
0

If you are OK with the messaging in the current IOException, you can have your bean throw it to abort startup:

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

If you want a more friendly error message than the default IOException (to better help point users at the error), you can throw your own exception with a customized exception message:

@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);
    }
}
Superman answered 20/12, 2021 at 19:30 Comment(0)
I
-2

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

Each SpringApplication will register a shutdown hook with the JVM to ensure that the ApplicationContext is closed gracefully on exit. All the standard Spring lifecycle callbacks (such as the DisposableBean interface, or the @PreDestroy annotation) can be used.

In addition, beans may implement the org.springframework.boot.ExitCodeGenerator interface if they wish to return a specific exit code when the application ends.

You should implement @PreDestroy methods which releases resources / files. Then during startup, when you detect some error, you could throw some RuntimeException - it start closing application context.

Interlink answered 2/12, 2016 at 21:18 Comment(4)
PreDestroy is for bean-level clean up, not for exiting the application.Olecranon
When Spring Boot application stops the context, all @PreDestroy bean methods are called. That was my point. It is obvious that when you have some class which have to do cleanup, code should be placed in the same class. This way abstraction does not leak from you class. Putting file resources cleanup in @PreDestroy is fine.Unfledged
read the question again. The question isn’t how to do cleanup at shutdown, but how to shut down the application. PreDestroy is just as relevant as the weather report.Olecranon
RuntimeException will trigger this. I mentioned @PreDestroy because usually when you need graceful shutdown you also need a place to put somewhere a cleanup code.Unfledged

© 2022 - 2024 — McMap. All rights reserved.