Home > Software engineering >  Spring boot how to use @PostConstruct correctly
Spring boot how to use @PostConstruct correctly

Time:09-16

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.

CodePudding user response:

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.

CodePudding user response:

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