Issue
I have multiple Spring InitializingBean
classes, which I'd like them to all run afterPropertiesSet()
in parallel. When I run a small example, however, they are being executed synchronously. Is there any way to execute them in parallel?
Below is an example initializing bean which can be used to test out what I'm referring to. When creating multiple classes like this (i.e. InitBeanOne
, InitBeanTwo
, ...), the logs show that they are being run synchronously.
One idea which I had in mind was to have a single initializing bean asynchronously initialize the desired classes. This is a last resort option, though, as I'd like to take advantage of the initializing beans for each class individually, and not have other dependent classes.
@Component
public class InitBean implements InitializingBean {
private final static Logger LOGGER = LoggerFactory.getLogger(InitBean.class);
@Override
public void afterPropertiesSet() throws Exception {
LOGGER.info("BEGIN: InitBean");
TimeUnit.SECONDS.sleep(5);
LOGGER.info("END: InitBean");
}
}
Solution
You should relocate the code to an event listening method, and mark the method with @Async
.
Make sure the Async functionality is correctly set up. See: How To Do @Async in Spring.
You should make the method be triggered when the Spring framework fires the ApplicationReadyEvent
.
@Component
public class InitBean {
private final static Logger LOGGER = LoggerFactory.getLogger(InitBean.class);
@Async
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) throws Exception {
LOGGER.info("BEGIN: onApplicationReady");
TimeUnit.SECONDS.sleep(5);
LOGGER.info("END: onApplicationReady");
}
}
Warning: By doing this, other methods may be called before/during the invocation of this method. If the method does any kind of initialization needed by those other methods, you need to handle that, e.g. using a CountDownLatch
.
UPDATE
If you need for the application to delay the completion of the startup sequence until all asynchronous methods have completed, I think you need to handle it yourself.
Create interface AsyncInitializingBean
with same method as InitializingBean
, then create a @Component
named AsyncBeanInitializer
auto-wiring a AsyncInitializingBean[]
(or List
), then have it execute all the methods using an ExecutorService
on ContextRefreshedEvent
.
@Component
public class InitBean implements AsyncInitializingBean { // <== Change interface (only change needed)
private final static Logger LOGGER = LoggerFactory.getLogger(InitBean.class);
@Override
public void afterPropertiesSet() throws Exception {
LOGGER.info("BEGIN: InitBean");
TimeUnit.SECONDS.sleep(5);
LOGGER.info("END: InitBean");
}
}
public interface AsyncInitializingBean {
void afterPropertiesSet() throws Exception;
}
@Component
public class AsyncBeanInitializer {
private final static Logger LOGGER = LoggerFactory.getLogger(AsyncBeanInitializer.class);
@Autowired(required = false)
private AsyncInitializingBean[] beans;
@EventListener
public void onContextRefreshed(@SuppressWarnings("unused") ContextRefreshedEvent event) throws Exception {
if (this.beans == null || this.beans.length == 0)
return;
ExecutorService executorService = Executors.newWorkStealingPool();
try {
AtomicInteger failed = new AtomicInteger();
for (AsyncInitializingBean bean : beans) {
executorService.submit(() -> {
try {
bean.afterPropertiesSet();
} catch (Exception e) {
failed.incrementAndGet();
LOGGER.error("Async afterPropertiesSet() method failed: " + e, e);
}
});
}
executorService.shutdown();
executorService.awaitTermination(60, TimeUnit.MINUTES);
if (failed.get() != 0)
throw new RuntimeException(failed.get() + " Async afterPropertiesSet() methods failed. See log for details.");
} finally {
executorService.shutdownNow();
}
}
}
Answered By - Andreas
Answer Checked By - Robin (JavaFixing Admin)