Manage asynchronous EventListener with replies
This commit makes sure to reject an `@EventListener` annotated method that also uses `@Async`. In such scenario, the method is invoked in a separate thread and the infrastructure has no handle on the actual reply, if any. The documentation has been improved to refer to that scenario. Issue: SPR-14113
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user