diff --git a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java index a57eb0c6120..fc1416fdaae 100644 --- a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java +++ b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java @@ -35,8 +35,10 @@ import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.expression.EvaluationContext; +import org.springframework.scheduling.annotation.Async; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; @@ -82,6 +84,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe public ApplicationListenerMethodAdapter(String beanName, Class targetClass, Method method) { + validateMethod(method); this.beanName = beanName; this.method = method; this.targetClass = targetClass; @@ -90,6 +93,14 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe this.methodKey = new AnnotatedElementKey(this.method, this.targetClass); } + private static void validateMethod(Method method) { + if (method.getReturnType() != void.class && + AnnotationUtils.findAnnotation(method, Async.class) != null) { + throw new IllegalStateException( + "Asynchronous @EventListener method is not allowed to return reply events: " + method); + } + } + /** * Initialize this instance. diff --git a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java index 89738660adb..ca8d8ae3766 100644 --- a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java @@ -155,6 +155,19 @@ public class AnnotationDrivenEventListenerTests { failingContext.refresh(); } + @Test + public void asyncWithReplyEventListener() { + AnnotationConfigApplicationContext failingContext = + new AnnotationConfigApplicationContext(); + failingContext.register(BasicConfiguration.class, + InvalidAsyncEventListener.class); + + this.thrown.expect(BeanInitializationException.class); + this.thrown.expectMessage(InvalidAsyncEventListener.class.getName()); + this.thrown.expectMessage("asyncCannotUseReply"); + failingContext.refresh(); + } + @Test public void simpleReply() { load(TestEventListener.class, ReplyEventListener.class); @@ -656,6 +669,17 @@ public class AnnotationDrivenEventListenerTests { } } + @Component + static class InvalidAsyncEventListener { + + @EventListener + @Async + public Integer asyncCannotUseReply(String payload) { + return 42; + } + + } + @Component static class ReplyEventListener extends AbstractTestEventListener { @@ -766,6 +790,7 @@ public class AnnotationDrivenEventListenerTests { this.eventCollector.addEvent(this, event); this.countDownLatch.countDown(); } + } @@ -869,7 +894,6 @@ public class AnnotationDrivenEventListenerTests { } - @EventListener @Retention(RetentionPolicy.RUNTIME) public @interface ConditionalEvent { diff --git a/src/asciidoc/core-beans.adoc b/src/asciidoc/core-beans.adoc index ba6fe773ec6..d241dad3ee5 100644 --- a/src/asciidoc/core-beans.adoc +++ b/src/asciidoc/core-beans.adoc @@ -8208,11 +8208,42 @@ method signature to return the event that should be published, something like: } ---- +NOTE: This feature is not supported for <>. + This new method will publish a new `ListUpdateEvent` for every `BlackListEvent` handled by the method above. If you need to publish several events, just return a `Collection` of events instead. -Finally if you need the listener to be invoked before another one, just add the `@Order` +[[context-functionality-events-async]] +==== Asynchronous Listeners + +If you want a particular listener to process events asynchronously, simply reuse the +<>: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @EventListener + @Async + public void processBlackListEvent(BlackListEvent event) { + // BlackListEvent is processed in a separate thread + } +---- + +Be aware of the following limitations when using asynchronous events: + +. If the event listener throws an `Exception` it will not be propagated to the caller, + check `AsyncUncaughtExceptionHandler` for more details. +. Such event listener cannot send replies. If you need to send another event as the + result of the processing, inject `ApplicationEventPublisher` to send the event + manually. + + +[[context-functionality-events-order] +==== Ordering Listeners + +If you need the listener to be invoked before another one, just add the `@Order` annotation to the method declaration: [source,java,indent=0]