Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@EventListener with @Async in Spring

Tags:

Trying to enable async event handling combining the @Async and @EventListener annotations, but I still see that the listener is running in the publishing thread.

The example you can find here:

@SpringBootApplication @EnableAsync class AsyncEventListenerExample {  static final Logger logger = LoggerFactory.getLogger(AsyncEventListenerExample.class);  @Bean TaskExecutor taskExecutor() {     return new SimpleAsyncTaskExecutor(); }   static class MedicalRecordUpdatedEvent {      private String id;      public MedicalRecordUpdatedEvent(String id) {         this.id = id;     }      @Override     public String toString() {         return "MedicalRecordUpdatedEvent{" +                 "id='" + id + '\'' +                 '}';     } }  @Component static class Receiver {      @EventListener     void handleSync(MedicalRecordUpdatedEvent event) {         logger.info("thread '{}' handling '{}' event", Thread.currentThread(), event);     }      @Async     @EventListener     void handleAsync(MedicalRecordUpdatedEvent event) {         logger.info("thread '{}' handling '{}' event", Thread.currentThread(), event);     }  }  @Component static class Producer {      private final ApplicationEventPublisher publisher;      public Producer(ApplicationEventPublisher publisher) {         this.publisher = publisher;     }      public void create(String id) {         publisher.publishEvent(new MedicalRecordUpdatedEvent(id));     }      @Async     public void asynMethod() {         logger.info("running async method with thread '{}'", Thread.currentThread());     } }  } 

and my test case:

@RunWith(SpringRunner.class) @SpringBootTest(classes = AsyncEventListenerExample.class) public class AsyncEventListenerExampleTests {  @Autowired Producer producer;  @Test public void createEvent() throws InterruptedException {      producer.create("foo");      //producer.asynMethod();       // A chance to see the logging messages before the JVM exists.     Thread.sleep(2000);  } } 

However in logs I see that both @EventListeners run in the main thread.

2016-05-12 08:52:43.184  INFO 18671 --- [           main] c.z.e.async2.AsyncEventListenerExample   : thread 'Thread[main,5,main]' handling 'MedicalRecordUpdatedEvent{id='foo'}' event 2016-05-12 08:52:43.186  INFO 18671 --- [           main] c.z.e.async2.AsyncEventListenerExample   : thread 'Thread[main,5,main]' handling 'MedicalRecordUpdatedEvent{id='foo'}' event 

The async infrastructure is initialised with @EnableAsync with an asynchronous TaskExecutor.

Not sure what I am doing wrong. Could you help?

Thanks.

Using Spring Boot 1.4.2.M2, so Spring 4.3.0.RC1

like image 387
Zoltan Altfatter Avatar asked May 12 '16 06:05

Zoltan Altfatter


People also ask

What is use of @async in Spring?

The @EnableAsync annotation switches on Spring's ability to run @Async methods in a background thread pool. This class also customizes the Executor by defining a new bean. Here, the method is named taskExecutor , since this is the specific method name for which Spring searches.

Are Spring events synchronous?

Spring allows us to create and publish custom events that by default are synchronous. This has a few advantages, such as the listener being able to participate in the publisher's transaction context.

Can we use @async on private method?

Never use @Async on top of a private method. In runtime, it will not able to create a proxy and, therefore, not work.

How is event handling done in Spring?

Event handling in the ApplicationContext is provided through the ApplicationEvent class and ApplicationListener interface. Hence, if a bean implements the ApplicationListener, then every time an ApplicationEvent gets published to the ApplicationContext, that bean is notified.


2 Answers

There was a regression in Spring Framework 4.3.0.RC1 that leads to that very issue you're having. If you use the SNAPSHOT, your project runs fine.

like image 186
Stephane Nicoll Avatar answered Apr 13 '23 00:04

Stephane Nicoll


onTicketUpdatedEvent runs in also main Thread with Spring Framework 4.2.4 Release as follows. But it runs in SimpleAsyncTaskExecutor if AsyncConfigurer is not implemented.

@EnableAsync(proxyTargetClass = true) @Component @Slf4j public class ExampleEventListener implements AsyncConfigurer {      @Async     @EventListener     public void onTicketUpdatedEvent(TicketEvent ticketEvent) {         log.debug("received ticket updated event");     }      @Override     public Executor getAsyncExecutor() {         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();         executor.setMaxPoolSize(100);         executor.initialize();         return executor;     }      @Override     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {         return null;     } } 
like image 39
john Avatar answered Apr 13 '23 00:04

john