Support for Collection-based return type

If an `@EventListener` annotated method returns a Collection or an Array,
each individual items are now published as an event instead of publishing
one event with said collection.

Issue: SPR-12733
This commit is contained in:
Stephane Nicoll 2015-02-20 09:40:00 +01:00
parent 7191050e26
commit 152a7b645f
3 changed files with 90 additions and 14 deletions

View File

@ -19,8 +19,8 @@ package org.springframework.context.event;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.UndeclaredThrowableException; import java.lang.reflect.UndeclaredThrowableException;
import java.util.Collection;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -37,6 +37,7 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -136,7 +137,27 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
protected void handleResult(Object result) { protected void handleResult(Object result) {
Assert.notNull(this.applicationContext, "ApplicationContext must no be null."); Assert.notNull(this.applicationContext, "ApplicationContext must no be null.");
this.applicationContext.publishEvent(result); if (result.getClass().isArray()) {
Object[] events = ObjectUtils.toObjectArray(result);
for (Object event : events) {
publishEvent(event);
}
}
else if (result instanceof Collection<?>) {
Collection<?> events = (Collection<?>) result;
for (Object event : events) {
publishEvent(event);
}
}
else {
publishEvent(result);
}
}
private void publishEvent(Object event) {
if (event != null) {
this.applicationContext.publishEvent(event);
}
} }

View File

@ -22,7 +22,9 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -157,7 +159,7 @@ public class AnnotationDrivenEventListenerTests {
this.eventCollector.assertNoEventReceived(replyEventListener); this.eventCollector.assertNoEventReceived(replyEventListener);
this.context.publishEvent(event); this.context.publishEvent(event);
this.eventCollector.assertEvent(replyEventListener, event); this.eventCollector.assertEvent(replyEventListener, event);
this.eventCollector.assertEvent(listener, new TestEvent(replyEventListener, event.getId(), event.msg)); // reply this.eventCollector.assertEvent(listener, new TestEvent(replyEventListener, event.getId(), "dummy")); // reply
this.eventCollector.assertTotalEventsCount(2); this.eventCollector.assertTotalEventsCount(2);
} }
@ -176,6 +178,55 @@ public class AnnotationDrivenEventListenerTests {
this.eventCollector.assertTotalEventsCount(1); this.eventCollector.assertTotalEventsCount(1);
} }
@Test
public void arrayReply() {
load(TestEventListener.class, ReplyEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, new String[]{"first", "second"});
ReplyEventListener replyEventListener = this.context.getBean(ReplyEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertNoEventReceived(replyEventListener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(replyEventListener, event);
this.eventCollector.assertEvent(listener, "first", "second"); // reply
this.eventCollector.assertTotalEventsCount(3);
}
@Test
public void collectionReply() {
load(TestEventListener.class, ReplyEventListener.class);
Set<Object> replies = new LinkedHashSet<>();
replies.add("first");
replies.add(4L);
replies.add("third");
AnotherTestEvent event = new AnotherTestEvent(this, replies);
ReplyEventListener replyEventListener = this.context.getBean(ReplyEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertNoEventReceived(replyEventListener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(replyEventListener, event);
this.eventCollector.assertEvent(listener, "first", "third"); // reply (no listener for 4L)
this.eventCollector.assertTotalEventsCount(3);
}
@Test
public void collectionReplyNullValue() {
load(TestEventListener.class, ReplyEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, Arrays.asList(null, "test"));
ReplyEventListener replyEventListener = this.context.getBean(ReplyEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertNoEventReceived(replyEventListener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(replyEventListener, event);
this.eventCollector.assertEvent(listener, "test");
this.eventCollector.assertTotalEventsCount(2);
}
@Test @Test
public void eventListenerWorksWithInterfaceProxy() throws Exception { public void eventListenerWorksWithInterfaceProxy() throws Exception {
load(ProxyTestBean.class); load(ProxyTestBean.class);
@ -464,15 +515,19 @@ public class AnnotationDrivenEventListenerTests {
@EventListener @EventListener
public Object handle(AnotherTestEvent event) { public Object handle(AnotherTestEvent event) {
collectEvent(event); collectEvent(event);
if (event.msg == null) { if (event.content == null) {
return null; return null;
} }
else if (event.msg.equals("String")) { else if (event.content instanceof String) {
return event.msg; String s = (String) event.content;
} if (s.equals("String")) {
else { return event.content;
return new TestEvent(this, event.getId(), event.msg); }
else {
return new TestEvent(this, event.getId(), s);
}
} }
return event.content;
} }
} }
@ -495,7 +550,7 @@ public class AnnotationDrivenEventListenerTests {
@Async @Async
public void handleAsync(AnotherTestEvent event) { public void handleAsync(AnotherTestEvent event) {
collectEvent(event); collectEvent(event);
if ("fail".equals(event.msg)) { if ("fail".equals(event.content)) {
countDownLatch.countDown(); countDownLatch.countDown();
throw new IllegalStateException("Test exception"); throw new IllegalStateException("Test exception");
} }
@ -517,7 +572,7 @@ public class AnnotationDrivenEventListenerTests {
@EventListener @EventListener
@Async @Async
public void handleAsync(AnotherTestEvent event) { public void handleAsync(AnotherTestEvent event) {
assertTrue(!Thread.currentThread().getName().equals(event.msg)); assertTrue(!Thread.currentThread().getName().equals(event.content));
collectEvent(event); collectEvent(event);
countDownLatch.countDown(); countDownLatch.countDown();
} }

View File

@ -22,11 +22,11 @@ package org.springframework.context.event.test;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class AnotherTestEvent extends IdentifiableApplicationEvent { public class AnotherTestEvent extends IdentifiableApplicationEvent {
public final String msg; public final Object content;
public AnotherTestEvent(Object source, String msg) { public AnotherTestEvent(Object source, Object content) {
super(source); super(source);
this.msg = msg; this.content = content;
} }
} }