From 152a7b645f61a55f19882219c237a7cee09fd620 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 20 Feb 2015 09:40:00 +0100 Subject: [PATCH] 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 --- .../ApplicationListenerMethodAdapter.java | 25 ++++++- .../AnnotationDrivenEventListenerTests.java | 73 ++++++++++++++++--- .../context/event/test/AnotherTestEvent.java | 6 +- 3 files changed, 90 insertions(+), 14 deletions(-) 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 d3392cd286..83f1f0fb32 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 @@ -19,8 +19,8 @@ package org.springframework.context.event; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Parameter; import java.lang.reflect.UndeclaredThrowableException; +import java.util.Collection; import org.apache.commons.logging.Log; 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.expression.EvaluationContext; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -136,7 +137,27 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe protected void handleResult(Object result) { 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); + } } 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 234fc15b4f..acaebc90f5 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 @@ -22,7 +22,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -157,7 +159,7 @@ public class AnnotationDrivenEventListenerTests { this.eventCollector.assertNoEventReceived(replyEventListener); this.context.publishEvent(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); } @@ -176,6 +178,55 @@ public class AnnotationDrivenEventListenerTests { 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 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 public void eventListenerWorksWithInterfaceProxy() throws Exception { load(ProxyTestBean.class); @@ -464,15 +515,19 @@ public class AnnotationDrivenEventListenerTests { @EventListener public Object handle(AnotherTestEvent event) { collectEvent(event); - if (event.msg == null) { + if (event.content == null) { return null; } - else if (event.msg.equals("String")) { - return event.msg; - } - else { - return new TestEvent(this, event.getId(), event.msg); + else if (event.content instanceof String) { + String s = (String) event.content; + if (s.equals("String")) { + return event.content; + } + else { + return new TestEvent(this, event.getId(), s); + } } + return event.content; } } @@ -495,7 +550,7 @@ public class AnnotationDrivenEventListenerTests { @Async public void handleAsync(AnotherTestEvent event) { collectEvent(event); - if ("fail".equals(event.msg)) { + if ("fail".equals(event.content)) { countDownLatch.countDown(); throw new IllegalStateException("Test exception"); } @@ -517,7 +572,7 @@ public class AnnotationDrivenEventListenerTests { @EventListener @Async public void handleAsync(AnotherTestEvent event) { - assertTrue(!Thread.currentThread().getName().equals(event.msg)); + assertTrue(!Thread.currentThread().getName().equals(event.content)); collectEvent(event); countDownLatch.countDown(); } diff --git a/spring-context/src/test/java/org/springframework/context/event/test/AnotherTestEvent.java b/spring-context/src/test/java/org/springframework/context/event/test/AnotherTestEvent.java index 59cae5a0f5..62cef1adaf 100644 --- a/spring-context/src/test/java/org/springframework/context/event/test/AnotherTestEvent.java +++ b/spring-context/src/test/java/org/springframework/context/event/test/AnotherTestEvent.java @@ -22,11 +22,11 @@ package org.springframework.context.event.test; @SuppressWarnings("serial") 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); - this.msg = msg; + this.content = content; } }