Spring boot how to use @PostConstruct correctly

Viewed 8110

Spring boot 2.5.4 I used @PostConstruct for the very first time in my service class. As following:-

   @Slf4j
   @Service
   @AllArgsConstructor

    public class FileMonitorService {

    private final AppProperties appProperties;

    private final WatchService watchService;

    private final RestTemplate restTemplate;

   @PostConstruct
   @Async
    public void startMonitoring() {
        FileUtils.setAppProperties(appProperties);
        FileUtils.setRestTemplate(restTemplate);
        FileUtils.readFilesForDirectory();
        log.info("START_MONITORING");
        try {
            WatchKey key;
            while ((key = watchService.take()) != null) {
                for (WatchEvent<?> event : key.pollEvents()) {
                    log.info("Event kind: {}; File affected: {}", event.kind(), event.context());
                   if((event.kind() == StandardWatchEventKinds.ENTRY_CREATE ||
                      event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) &&
                      event.context().toString().contains(".xml")){
                       try {
                           restTemplateRequest(event.context().toString()+" processing");
                           FileUtils.readXml(Path.of(FileUtils.getFileAbsolutePath(appProperties.getDataIn()),
                                   event.context().toString()));
                       }catch (Exception e){
                           log.error("startMonitoring Exception: "+e.getMessage());
                       }
                   }
                }
                key.reset();
            }
        } catch (InterruptedException e) {
            log.warn("startMonitoring: interrupted exception for monitoring service: "+e.getMessage());
        }
    }
 }

This method is called as soon as app launched. That is my requirements to process all file as soon as the app starts. I have controller as following:-

@RestController
@RequestMapping("/xml")
public class FileController {

    @Autowired
    FileMonitorService fileMonitorService;

    @SneakyThrows
    @GetMapping("/restart")
    public String restartFileMonitoring(){
        fileMonitorService.startMonitoring();
        return "File monitoring restarted started successfully";
    }
}

My app starts on port 8080 and no exception at all. But when I get call this end point localhost:8080/xml/restart

It is not reachable. If I comment out the @PostConstruct then I can call the end point. I am confused how to use this annotation properly. What is wrong in my code?

Update info:-

    :: Spring Boot ::                (v2.5.4)

2021-09-14 18:23:21.521  INFO 71192 --- [           main] c.f.i.task.BatchProcessorApplication     : Starting BatchProcessorApplication using Java 14.0.2 on dev with PID 71192 (/home/dev/Desktop/batch-processor/batch-processor/target/classes started by dev in /home/dev/Desktop/batch-processor/batch-processor)
2021-09-14 18:23:21.523  INFO 71192 --- [           main] c.f.i.task.BatchProcessorApplication     : No active profile set, falling back to default profiles: default
2021-09-14 18:23:22.485  INFO 71192 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-09-14 18:23:22.495  INFO 71192 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-09-14 18:23:22.495  INFO 71192 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.52]
2021-09-14 18:23:22.564  INFO 71192 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-09-14 18:23:22.564  INFO 71192 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 988 ms
File to monitor: /home/dev/Desktop/batch-processor/batch-processor/data/in
2021-09-14 18:23:22.647  INFO 71192 --- [           main] c.f.i.task.config.FileMonitorConfig      : MONITORING_DIR: /home/dev/Desktop/batch-processor/batch-processor/data/in/
2021-09-14 18:23:22.667  INFO 71192 --- [           main] c.f.i.task.service.FileMonitorService    : START_MONITORING

That is the log when I run the app. After debugging I found that while ((key = watchService.take()) != null) { call never returns until I copy some XML file as this app process xml files. Then I copy any xml file in the monitoring dir. I was expecting that @Async it will run in back ground thread in async mode. How to monitory this dir in background thread? So the caller of this method won't be blocked.

2 Answers

PostContstruct semantics


The PostConstruct annotation is part of JSR 330 (Dependency Injection) and is not a Spring custom annotation.

The annotation specification dictates that the annotated method MUST run before the service being injected into context or translated into a service.

Spring supports the PostConstruct lifecycle hook allowing to perform extra post-initialization actions once a bean has been initialized, i.e., it had all its dependencies injected.

Async semantics


The Async annotation on the other hand is a Spring specific annotation allowing to mark a method or a type as being a candidate for asynchronous execution.

Alternative


In a case where you are interested into starting a background process as long as you application starts, you should better use the application lifecycle events and more specifically the ApplicationReadyEvent to spin your monitoring activity:

@Slf4j
@Service
@AllArgsConstructor
public class FileMonitorService {

    private final AppProperties appProperties;

    private final WatchService watchService;

    private final RestTemplate restTemplate;

    @EventListener(ApplicationReadyEvent.class)
    @Async
    public void startMonitoring() {
        // ...
    }
}

And don't forget to add the @EnableAsync annotation on your Spring Boot configuration type to activate the asynchronous processing feature.

For your case, you don't need to use @PostConstruct and that is why its working when removing the @PostConstruct

to simplify, @PostConstruct is considered as a class empty constructor but it make sure all the Beans are loaded before being called

Related