Olayinka
Olayinka

Reputation: 2845

Method annotated with @Async not called asynchronously and ContextRefreshedEvent is received before application context starts

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.

Scenario 1

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

    }

}

Scenario 2

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

Answers (1)

i8r
i8r

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

Related Questions