简体   繁体   中英

How to run @PostConstruct non-blocking in Spring?

@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?

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");
        }
    }

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();
}

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

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
        }
    }
  }
}

you would better write that code part inside of a CommandLineRunner 's run method. that method will be allowed to execute the blocking code. and if you want to terminate the application you can check the conditions and throw an exception to terminate the application running.

@Component
public class ApplicationInitializer implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        Boolean isTrue = Mono.just(false)
                .block();
        if (Boolean.FALSE.equals(isTrue)) {
            throw new RuntimeException("The application can't be run due to the value is false.");
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM