How to run @PostConstruct non-blocking in Spring?
Asked Answered
C

4

8
@PostConstruct
public void performStateChecks() {
   throw new RuntimeException("test");
}

If I start a spring application with code above, it will prevent the application to start.

What I'm looking for is to execute a method directly after startup, but async. Means, it should not delay the startup, and it should not prevent the application to run even on failure.

How can I make the initialization async?

Chromite answered 17/3, 2021 at 10:6 Comment(4)
use @Async? Or just a simple TaskExecutor?Oocyte
@Async @PostConstruct is not executed async. So I could of course call another async bean from within postconstruct, if that's the only chance.Chromite
Don't use @PostConstruct for that. It is executed as part of the application init/startup and errors will stop that (as you noticed). So don't use @PostConstruct for that and/or use proper exception handling in that method. BUt you are probably better of using an event listener to initialize something.Cholecystectomy
@Oocyte you absolute genius. I'd forgotten about @ Ansync and was about to write a whole Runnable Thread type class for my code until I saw this. Thank you.Illene
B
14

You can use EventListener instead of PostConstruct, it supports @Async:

@Service
public class MyService {    
    @Async
    @EventListener(ApplicationStartedEvent.class)
    public void performStateChecks() {
        throw new RuntimeException("test");
    }
 }

Don't forget enable async support by @EnableAsync annotation

You can also use some other event, see inheritors of SpringApplicationEvent class

Broeder answered 17/3, 2021 at 10:39 Comment(0)
P
3

The easiest way I can see is by using EventListeners and async task executors.

Adding this code snippet would do the work:

    @Component
    public class AsyncStartupRunner {

        @Bean(name = "applicationEventMulticaster")
        public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
            SimpleApplicationEventMulticaster eventMulticaster =
                    new SimpleApplicationEventMulticaster();

            eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
            return eventMulticaster;
        }

        @EventListener(ApplicationReadyEvent.class)
        public void executeAfterStartup() {
            throw new RuntimeException("Oops");
        }
    }
Postoperative answered 17/3, 2021 at 10:32 Comment(0)
S
1

There are multiple ways this can be done.

First, simple one-liner solution is to create and start a new thread;

@PostConstruct
public void performStateChecks() {
  new Thread(() -> { throw new RuntimeException("test"); }).start();
}

The thrown exception only interrupts the separate thread and doesn't block or prevent the application startup. This is useful if you are not interested in result or outcome of the task. Note this is not recommended as it starts separate thread outside spring managed context.

Second is to use executor service and submit a task to it. Spring provides a default ThreadPoolTaskExecutor which can be used to submit the tasks. This will allow you to have access to the future object of the task and do something with it later on;

private final ThreadPoolTaskExecutor executor;  // inject via constructor
@PostConstruct
public void performStateChecks() {
    Future<?> future = executor.submit(() -> {
      throw new RuntimeException("test");
    });
    // do something with the future later on
}

If you have multiple such methods and requirements for various services/classes etc then create a new AsyncService class to do the actual work and annotate those methods with @Async. inject the AsyncService wherever you need via constructor and then call the required method;

@EnableAsync
@Component
public class AsyncService {

    @Async
    public void doActualTest() {
      throw new RuntimeException("test");
    }
}

Then use it like this;

private final AsyncService asyncService; // make sure to inject this via constructor

@PostConstruct 
public void performStateChecks() {
    asyncService.doActualTest();
}
Subtlety answered 17/3, 2021 at 10:24 Comment(6)
I wouldn't call the first 2 solutions, as those will start their own threads. YOu should either delegate to an @Async method (as your third solution) or inject a managed thread pool to launch the threads. You shouldn't be starting threads yourself.Cholecystectomy
You should definitly not start your own threads, also creating an unshared executor service seems like a code smellOocyte
@M.Deinum thanks and yes agree with your comment. updated the answerSubtlety
@Subtlety - Can you remind me why constructor injection of the Service is required here and Autowiring won't work?Brenda
"Async can not be used in conjunction with lifecycle callbacks such as PostConstruct. To asynchonously initialize Spring beans you currently have to use a separate initializing Spring bean that invokes the Async annotated method on the target then." - see docs.spring.io/spring-framework/docs/3.0.x/reference/…Brenda
@Brenda the link you have referenced literally shows same example of how to circumvent the restriction of not being able to use Async in conjunction with PostConstruct which is to call async method of different service from PostConstruct. When it says Async can not be used in conjunction with lifecycle callbacks such as PostConstruct; it means you can't have both of these annotations on a single method.Subtlety
H
0

You can remove @PostConstruct from your method and let that method be a normal method. You can then manualy invoke it when the ApplicatioinContext is already loaded and the application has already started.

 @SpringBootApplication
 public class ServiceLauncher {

 public static void main(String[] args) {

   ConfigurableApplicationContext context = new SpringApplication(ServiceLauncher.class).run(args);

    try {
        context.getBean(YourBean.class).performStateChecks(); // <-- this will run only after context initialization finishes
        } catch (Exception e) {
        //catch any exception here so it does not go down
        }
    }
  }
}
Horrify answered 17/3, 2021 at 10:20 Comment(1)
I don't really like this solution, as it is placed directly inside the main methodOocyte

© 2022 - 2024 — McMap. All rights reserved.