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.
Advertisement
Answer
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.