Reputation: 2845
I present two scenario, one with a bean registered using the bean factory and the other with the bean created from auto-scanning the packages for annotated definitions (e.g @Component
). The bean class listens to the ContextRefreshedEvent
using a method annotated with @EventListener
and also with @Async
so that it is called asynchronously.
A singleton of class BlockingListener
is created and registered with the bean factory. This is done at the initialisation of another Bean as described below in the method afterPropertiesSet
. The ContextRefreshedEvent
is received but doesn't exit, and thus the Application doesn't start. It remained blocked.
@EnableAsync
@EnableScheduling
@EnableAutoConfiguration
@SpringBootApplication
public class SampleApp implements InitializingBean {
private final DefaultListableBeanFactory beanFactory;
@Autowired
public SampleApp(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public void afterPropertiesSet() {
String beanName = "blocking-listener";
Object bean = new BlockingListener();
beanFactory.registerBeanDefinition(beanName, rootBeanDefinition(bean.getClass()).getBeanDefinition());
beanFactory.registerSingleton(beanName, bean);
}
public static void main(final String... args) {
SpringApplication.run(SampleApp.class, args);
}
public static class BlockingListener {
@Async
@EventListener(ContextRefreshedEvent.class)
void block() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
}
}
The class BlockingListener
is annotated with @Component
and the bean is auto-detected and created. The ContextRefreshedEvent
is received but doesn't exit, and yet the Application starts.
@EnableAsync
@EnableScheduling
@EnableAutoConfiguration
@SpringBootApplication
public class SampleApp {
private final DefaultListableBeanFactory beanFactory;
@Autowired
public SampleApp(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public static void main(final String... args) {
SpringApplication.run(SampleApp.class, args);
}
@Component
public static class BlockingListener {
@Async
@EventListener(ContextRefreshedEvent.class)
void block() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
}
}
The expected behaviour is with the second scenario since the ContextRefreshedEvent is expected to be published after the context is successfully started. However I can't figure out why the bean registered dynamically with the bean factory received the event before the context started and why it blocked the context from starting up.
Upvotes: 4
Views: 388
Reputation: 51
In scenario 1 the call of block()
does not happen asynchronously because the annotation @Async
doesn't become effective.
@Async
is working through a BeanPostProcessor
, namely AsyncAnnotationBeanPostProcessor
that is wrapping the instance with a proxy. But when you manually add a bean to the bean factory, the post processors are not applied.
What you can do in the given setting, is applying the post processors manually as:
bean = beanFactory.initializeBean(bean, beanName);
beanFactory.registerSingleton(beanName, bean);
Upvotes: 2