17

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?

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
Zhubin Salehi
  • 281
  • 1
  • 5
  • 14

4 Answers4

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().

Kyle Anderson
  • 6,801
  • 1
  • 29
  • 41
  • 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 hierarchy` – Zhubin Salehi Dec 02 '16 at 21:47
  • 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'; ` – Zhubin Salehi Dec 02 '16 at 21:50
  • @ZhubinSalehi I've added a more detailed example showing exactly how and where to call the close() method. Hope that helps. – Kyle Anderson Dec 02 '16 at 22:31
  • 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! – Zhubin Salehi Dec 09 '16 at 18:22
  • @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. – Kyle Anderson Dec 09 '16 at 19:01
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.

Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219
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);
    }
}
M. Justin
  • 14,487
  • 7
  • 91
  • 130
-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.

Michał Mielec
  • 1,582
  • 1
  • 14
  • 38
  • `PreDestroy` is for bean-level clean up, not for exiting the application. – Abhijit Sarkar Jan 09 '19 at 01:28
  • 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. – Michał Mielec Jan 09 '19 at 12:03
  • 1
    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. – Abhijit Sarkar Jan 09 '19 at 16:57
  • ```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. – Michał Mielec Feb 20 '19 at 16:45