Annotation-based event listeners

Add support for annotation-based event listeners. Enabled automatically
when using Java configuration or can be enabled explicitly via the
regular <context:annotation-driven/> XML element. Detect methods of
managed beans annotated with @EventListener, either directly or through
a meta-annotation.

Annotated methods must define the event type they listen to as a single
parameter argument. Events are automatically filtered out according to
the method signature. When additional runtime filtering is required, one
can specify the `condition` attribute of the annotation that defines a
SpEL expression that should match to actually invoke the method for a
particular event. The root context exposes the actual `event`
(`#root.event`) and method arguments (`#root.args`). Individual method
arguments are also exposed via either the `a` or `p` alias (`#a0` refers
to the first method argument). Finally, methods arguments are exposed via
their names if that information can be discovered.

Events can be either an ApplicationEvent or any arbitrary payload. Such
payload is wrapped automatically in a PayloadApplicationEvent and managed
explicitly internally. As a result, users can now publish and listen
for arbitrary objects.

If an annotated method has a return value, an non null result is actually
published as a new event, something like:

@EventListener
public FooEvent handle(BarEvent event) { ... }

Events can be handled in an aynchronous manner by adding `@Async` to the
event method declaration and enabling such infrastructure. Events can
also be ordered by adding an `@Order` annotation to the event method.

Issue: SPR-11622
This commit is contained in:
Stephane Nicoll
2015-01-23 11:57:58 +01:00
parent 6d6422acde
commit f0fca890bb
31 changed files with 2288 additions and 134 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ public class ClassPathBeanDefinitionScannerTests {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, beanCount);
assertEquals(11, beanCount);
assertTrue(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("fooServiceImpl"));
assertTrue(context.containsBean("stubFooDao"));
@@ -66,6 +66,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
context.refresh();
FooServiceImpl service = context.getBean("fooServiceImpl", FooServiceImpl.class);
assertTrue(context.getDefaultListableBeanFactory().containsSingleton("myNamedComponent"));
@@ -98,7 +99,7 @@ public class ClassPathBeanDefinitionScannerTests {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, beanCount);
assertEquals(11, beanCount);
scanner.scan(BASE_PACKAGE);
assertTrue(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("fooServiceImpl"));
@@ -218,11 +219,12 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomComponent.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(5, beanCount);
assertEquals(6, beanCount);
assertTrue(context.containsBean("messageBean"));
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
@@ -231,7 +233,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomComponent.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(5, beanCount);
assertEquals(6, beanCount);
assertTrue(context.containsBean("messageBean"));
assertFalse(context.containsBean("serviceInvocationCounter"));
assertFalse(context.containsBean("fooServiceImpl"));
@@ -241,6 +243,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
@@ -249,7 +252,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomComponent.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(11, beanCount);
assertEquals(12, beanCount);
assertTrue(context.containsBean("messageBean"));
assertTrue(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("fooServiceImpl"));
@@ -259,6 +262,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
@@ -267,7 +271,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
scanner.addExcludeFilter(new AnnotationTypeFilter(Aspect.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(9, beanCount);
assertEquals(10, beanCount);
assertFalse(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("fooServiceImpl"));
assertTrue(context.containsBean("stubFooDao"));
@@ -276,6 +280,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
@@ -284,7 +289,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
scanner.addExcludeFilter(new AssignableTypeFilter(FooService.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(9, beanCount);
assertEquals(10, beanCount);
assertFalse(context.containsBean("fooServiceImpl"));
assertTrue(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("stubFooDao"));
@@ -293,6 +298,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
@@ -320,7 +326,7 @@ public class ClassPathBeanDefinitionScannerTests {
scanner.addExcludeFilter(new AssignableTypeFilter(FooService.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Aspect.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(8, beanCount);
assertEquals(9, beanCount);
assertFalse(context.containsBean("fooServiceImpl"));
assertFalse(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("stubFooDao"));
@@ -329,6 +335,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
@@ -337,7 +344,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, beanCount);
assertEquals(11, beanCount);
assertFalse(context.containsBean("fooServiceImpl"));
assertTrue(context.containsBean("fooService"));
assertTrue(context.containsBean("serviceInvocationCounter"));
@@ -347,6 +354,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
@@ -356,7 +364,7 @@ public class ClassPathBeanDefinitionScannerTests {
GenericApplicationContext multiPackageContext = new GenericApplicationContext();
ClassPathBeanDefinitionScanner multiPackageScanner = new ClassPathBeanDefinitionScanner(multiPackageContext);
int singlePackageBeanCount = singlePackageScanner.scan(BASE_PACKAGE);
assertEquals(10, singlePackageBeanCount);
assertEquals(11, singlePackageBeanCount);
multiPackageScanner.scan(BASE_PACKAGE, "org.springframework.dao.annotation");
// assertTrue(multiPackageBeanCount > singlePackageBeanCount);
}
@@ -367,7 +375,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
int initialBeanCount = context.getBeanDefinitionCount();
int scannedBeanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, scannedBeanCount);
assertEquals(11, scannedBeanCount);
assertEquals(scannedBeanCount, context.getBeanDefinitionCount() - initialBeanCount);
int addedBeanCount = scanner.scan("org.springframework.aop.aspectj.annotation");
assertEquals(initialBeanCount + scannedBeanCount + addedBeanCount, context.getBeanDefinitionCount());
@@ -380,7 +388,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, beanCount);
assertEquals(11, beanCount);
context.refresh();
FooServiceImpl fooService = context.getBean("fooService", FooServiceImpl.class);

View File

@@ -30,19 +30,19 @@ public abstract class AbstractApplicationEventListenerTests {
protected ResolvableType getGenericApplicationEventType(String fieldName) {
try {
return ResolvableType.forField(GenericApplicationEvents.class.getField(fieldName));
return ResolvableType.forField(TestEvents.class.getField(fieldName));
}
catch (NoSuchFieldException e) {
throw new IllegalStateException("No such field on Events '" + fieldName + "'");
}
}
protected static class GenericApplicationEvent<T>
protected static class GenericTestEvent<T>
extends ApplicationEvent {
private final T payload;
public GenericApplicationEvent(Object source, T payload) {
public GenericTestEvent(Object source, T payload) {
super(source);
this.payload = payload;
}
@@ -53,76 +53,72 @@ public abstract class AbstractApplicationEventListenerTests {
}
protected static class StringEvent extends GenericApplicationEvent<String> {
protected static class StringEvent extends GenericTestEvent<String> {
public StringEvent(Object source, String payload) {
super(source, payload);
}
}
protected static class LongEvent extends GenericApplicationEvent<Long> {
protected static class LongEvent extends GenericTestEvent<Long> {
public LongEvent(Object source, Long payload) {
super(source, payload);
}
}
protected <T> GenericApplicationEvent<T> createGenericEvent(T payload) {
return new GenericApplicationEvent<>(this, payload);
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
static class GenericEventListener implements ApplicationListener<GenericApplicationEvent<?>> {
static class GenericEventListener implements ApplicationListener<GenericTestEvent<?>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<?> event) {
public void onApplicationEvent(GenericTestEvent<?> event) {
}
}
static class ObjectEventListener implements ApplicationListener<GenericApplicationEvent<Object>> {
static class ObjectEventListener implements ApplicationListener<GenericTestEvent<Object>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<Object> event) {
public void onApplicationEvent(GenericTestEvent<Object> event) {
}
}
static class UpperBoundEventListener
implements ApplicationListener<GenericApplicationEvent<? extends RuntimeException>> {
implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<? extends RuntimeException> event) {
public void onApplicationEvent(GenericTestEvent<? extends RuntimeException> event) {
}
}
static class StringEventListener implements ApplicationListener<GenericApplicationEvent<String>> {
static class StringEventListener implements ApplicationListener<GenericTestEvent<String>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<String> event) {
public void onApplicationEvent(GenericTestEvent<String> event) {
}
}
static class RawApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
}
}
@SuppressWarnings("unused")
static class GenericApplicationEvents {
static class TestEvents {
public GenericApplicationEvent<?> wildcardEvent;
public ApplicationEvent applicationEvent;
public GenericApplicationEvent<String> stringEvent;
public GenericTestEvent<?> wildcardEvent;
public GenericApplicationEvent<Long> longEvent;
public GenericTestEvent<String> stringEvent;
public GenericApplicationEvent<IllegalStateException> illegalStateExceptionEvent;
public GenericTestEvent<Long> longEvent;
public GenericApplicationEvent<IOException> ioExceptionEvent;
public GenericTestEvent<IllegalStateException> illegalStateExceptionEvent;
public GenericTestEvent<IOException> ioExceptionEvent;
}

View File

@@ -0,0 +1,619 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.context.event.test.AbstractIdentifiable;
import org.springframework.context.event.test.AnotherTestEvent;
import org.springframework.context.event.test.EventCollector;
import org.springframework.context.event.test.Identifiable;
import org.springframework.context.event.test.TestEvent;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* @author Stephane Nicoll
*/
public class AnnotationDrivenEventListenerTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private ConfigurableApplicationContext context;
private EventCollector eventCollector;
private CountDownLatch countDownLatch; // 1 call by default
@After
public void closeContext() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void simpleEventJavaConfig() {
load(TestEventListener.class);
TestEvent event = new TestEvent(this, "test");
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void simpleEventXmlConfig() {
this.context = new ClassPathXmlApplicationContext(
"org/springframework/context/event/simple-event-configuration.xml");
TestEvent event = new TestEvent(this, "test");
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector = getEventCollector(this.context);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void metaAnnotationIsDiscovered() {
load(MetaAnnotationListenerTestBean.class);
MetaAnnotationListenerTestBean bean = context.getBean(MetaAnnotationListenerTestBean.class);
this.eventCollector.assertNoEventReceived(bean);
TestEvent event = new TestEvent();
this.context.publishEvent(event);
this.eventCollector.assertEvent(bean, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void contextEventsAreReceived() {
load(ContextEventListener.class);
ContextEventListener listener = this.context.getBean(ContextEventListener.class);
List<Object> events = this.eventCollector.getEvents(listener);
assertEquals("Wrong number of initial context events", 1, events.size());
assertEquals(ContextRefreshedEvent.class, events.get(0).getClass());
this.context.stop();
List<Object> eventsAfterStop = this.eventCollector.getEvents(listener);
assertEquals("Wrong number of context events on shutdown", 2, eventsAfterStop.size());
assertEquals(ContextStoppedEvent.class, eventsAfterStop.get(1).getClass());
this.eventCollector.assertTotalEventsCount(2);
}
@Test
public void methodSignatureNoEvent() {
AnnotationConfigApplicationContext failingContext =
new AnnotationConfigApplicationContext();
failingContext.register(BasicConfiguration.class,
InvalidMethodSignatureEventListener.class);
thrown.expect(BeanInitializationException.class);
thrown.expectMessage(InvalidMethodSignatureEventListener.class.getName());
thrown.expectMessage("cannotBeCalled");
failingContext.refresh();
}
@Test
public void simpleReply() {
load(TestEventListener.class, ReplyEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, "dummy");
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, new TestEvent(replyEventListener, event.getId(), event.msg)); // reply
this.eventCollector.assertTotalEventsCount(2);
}
@Test
public void nullReplyIgnored() {
load(TestEventListener.class, ReplyEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, null); // No response
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.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void eventListenerWorksWithInterfaceProxy() throws Exception {
load(ProxyTestBean.class);
SimpleService proxy = this.context.getBean(SimpleService.class);
assertTrue("bean should be a proxy", proxy instanceof Advised);
this.eventCollector.assertNoEventReceived(proxy.getId());
TestEvent event = new TestEvent();
this.context.publishEvent(event);
this.eventCollector.assertEvent(proxy.getId(), event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void methodNotAvailableOnProxyIsDetected() throws Exception {
thrown.expect(BeanInitializationException.class);
thrown.expectMessage("handleIt2");
load(InvalidProxyTestBean.class);
}
@Test
public void eventListenerWorksWithCglibProxy() throws Exception {
load(CglibProxyTestBean.class);
CglibProxyTestBean proxy = this.context.getBean(CglibProxyTestBean.class);
assertTrue("bean should be a cglib proxy", AopUtils.isCglibProxy(proxy));
this.eventCollector.assertNoEventReceived(proxy.getId());
TestEvent event = new TestEvent();
this.context.publishEvent(event);
this.eventCollector.assertEvent(proxy.getId(), event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void asyncProcessingApplied() throws InterruptedException {
loadAsync(AsyncEventListener.class);
String threadName = Thread.currentThread().getName();
AnotherTestEvent event = new AnotherTestEvent(this, threadName);
AsyncEventListener listener = this.context.getBean(AsyncEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
countDownLatch.await(2, TimeUnit.SECONDS);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void exceptionPropagated() {
load(ExceptionEventListener.class);
TestEvent event = new TestEvent(this, "fail");
ExceptionEventListener listener = this.context.getBean(ExceptionEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
try {
this.context.publishEvent(event);
fail("An exception should have thrown");
}
catch (IllegalStateException e) {
assertEquals("Wrong exception", "Test exception", e.getMessage());
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
}
@Test
public void exceptionNotPropagatedWithAsync() throws InterruptedException {
loadAsync(ExceptionEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, "fail");
ExceptionEventListener listener = this.context.getBean(ExceptionEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
countDownLatch.await(2, TimeUnit.SECONDS);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void listenerWithSimplePayload() {
load(TestEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent("test");
this.eventCollector.assertEvent(listener, "test");
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void listenerWithNonMatchingPayload() {
load(TestEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(123L);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(0);
}
@Test
public void replyWithPayload() {
load(TestEventListener.class, ReplyEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, "String");
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, "String"); // reply
this.eventCollector.assertTotalEventsCount(2);
}
@Test
public void listenerWithGenericApplicationEvent() {
load(GenericEventListener.class);
GenericEventListener listener = this.context.getBean(GenericEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent("TEST");
this.eventCollector.assertEvent(listener, "TEST");
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void conditionMatch() {
long timestamp = System.currentTimeMillis();
load(ConditionalEventListener.class);
TestEvent event = new TestEvent(this, "OK");
TestEventListener listener = this.context.getBean(ConditionalEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
this.context.publishEvent("OK");
this.eventCollector.assertEvent(listener, event, "OK");
this.eventCollector.assertTotalEventsCount(2);
this.context.publishEvent(timestamp);
this.eventCollector.assertEvent(listener, event, "OK", timestamp);
this.eventCollector.assertTotalEventsCount(3);
}
@Test
public void conditionDoesNotMatch() {
long maxLong = Long.MAX_VALUE;
load(ConditionalEventListener.class);
TestEvent event = new TestEvent(this, "KO");
TestEventListener listener = this.context.getBean(ConditionalEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(0);
this.context.publishEvent("KO");
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(0);
this.context.publishEvent(maxLong);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(0);
}
@Test
public void orderedListeners() {
load(OrderedTestListener.class);
OrderedTestListener listener = this.context.getBean(OrderedTestListener.class);
assertTrue(listener.order.isEmpty());
this.context.publishEvent("whatever");
assertThat(listener.order, contains("first", "second", "third"));
}
private void load(Class<?>... classes) {
List<Class<?>> allClasses = new ArrayList<>();
allClasses.add(BasicConfiguration.class);
allClasses.addAll(Arrays.asList(classes));
doLoad(allClasses.toArray(new Class<?>[allClasses.size()]));
}
private void loadAsync(Class<?>... classes) {
List<Class<?>> allClasses = new ArrayList<>();
allClasses.add(AsyncConfiguration.class);
allClasses.addAll(Arrays.asList(classes));
doLoad(allClasses.toArray(new Class<?>[allClasses.size()]));
}
private void doLoad(Class<?>... classes) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(classes);
this.eventCollector = ctx.getBean(EventCollector.class);
this.countDownLatch = ctx.getBean(CountDownLatch.class);
this.context = ctx;
}
private EventCollector getEventCollector(ConfigurableApplicationContext context) {
return context.getBean(EventCollector.class);
}
@Configuration
static class BasicConfiguration {
@Bean
public EventCollector eventCollector() {
return new EventCollector();
}
@Bean
public CountDownLatch testCountDownLatch() {
return new CountDownLatch(1);
}
}
static abstract class AbstractTestEventListener extends AbstractIdentifiable {
@Autowired
private EventCollector eventCollector;
protected void collectEvent(Object content) {
this.eventCollector.addEvent(this, content);
}
}
@Component
static class TestEventListener extends AbstractTestEventListener {
@EventListener
public void handle(TestEvent event) {
collectEvent(event);
}
@EventListener
public void handleString(String content) {
collectEvent(content);
}
}
@EventListener
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface FooListener {
}
@Component
static class MetaAnnotationListenerTestBean extends AbstractTestEventListener {
@FooListener
public void handleIt(TestEvent event) {
collectEvent(event);
}
}
@Component
static class ContextEventListener extends AbstractTestEventListener {
@EventListener
public void handleContextEvent(ApplicationContextEvent event) {
collectEvent(event);
}
}
@Component
static class InvalidMethodSignatureEventListener {
@EventListener
public void cannotBeCalled(String s, Integer what) {
}
}
@Component
static class ReplyEventListener extends AbstractTestEventListener {
@EventListener
public Object handle(AnotherTestEvent event) {
collectEvent(event);
if (event.msg == null) {
return null;
}
else if (event.msg.equals("String")) {
return event.msg;
}
else {
return new TestEvent(this, event.getId(), event.msg);
}
}
}
@Component
static class ExceptionEventListener extends AbstractTestEventListener {
@Autowired
private CountDownLatch countDownLatch;
@EventListener
public void handle(TestEvent event) {
collectEvent(event);
if ("fail".equals(event.msg)) {
throw new IllegalStateException("Test exception");
}
}
@EventListener
@Async
public void handleAsync(AnotherTestEvent event) {
collectEvent(event);
if ("fail".equals(event.msg)) {
countDownLatch.countDown();
throw new IllegalStateException("Test exception");
}
}
}
@Configuration
@Import(BasicConfiguration.class)
@EnableAsync(proxyTargetClass = true)
static class AsyncConfiguration {
}
@Component
static class AsyncEventListener extends AbstractTestEventListener {
@Autowired
private CountDownLatch countDownLatch;
@EventListener
@Async
public void handleAsync(AnotherTestEvent event) {
assertTrue(!Thread.currentThread().getName().equals(event.msg));
collectEvent(event);
countDownLatch.countDown();
}
}
interface SimpleService extends Identifiable {
@EventListener
void handleIt(TestEvent event);
}
@Component
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
static class ProxyTestBean extends AbstractIdentifiable implements SimpleService {
@Autowired
private EventCollector eventCollector;
@Override
public void handleIt(TestEvent event) {
eventCollector.addEvent(this, event);
}
}
@Component
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
static class InvalidProxyTestBean extends ProxyTestBean {
@EventListener // does not exist on any interface so it should fail
public void handleIt2(TestEvent event) {
}
}
@Component
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
static class CglibProxyTestBean extends AbstractTestEventListener {
@EventListener
public void handleIt(TestEvent event) {
collectEvent(event);
}
}
@Component
static class GenericEventListener extends AbstractTestEventListener {
@EventListener
public void handleString(PayloadApplicationEvent<String> event) {
collectEvent(event.getPayload());
}
}
@Component
static class ConditionalEventListener extends TestEventListener {
@EventListener(condition = "'OK'.equals(#root.event.msg)")
@Override
public void handle(TestEvent event) {
super.handle(event);
}
@Override
@EventListener(condition = "'OK'.equals(#content)")
public void handleString(String content) {
super.handleString(content);
}
@EventListener(condition = "#root.event.timestamp > #p0")
public void handleTimestamp(Long timestamp) {
collectEvent(timestamp);
}
}
@Component
static class OrderedTestListener extends TestEventListener {
public final List<String> order = new ArrayList<>();
@EventListener
@Order(50)
public void handleThird(String payload) {
order.add("third");
}
@EventListener
@Order(-50)
public void handleFirst(String payload) {
order.add("first");
}
@EventListener
public void handleSecond(String payload) {
order.add("second");
}
}
}

View File

@@ -58,19 +58,19 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
@Test
public void multicastGenericEvent() {
multicastEvent(true, StringEventListener.class, createGenericEvent("test"),
multicastEvent(true, StringEventListener.class, createGenericTestEvent("test"),
getGenericApplicationEventType("stringEvent"));
}
@Test
public void multicastGenericEventWrongType() {
multicastEvent(false, StringEventListener.class, createGenericEvent(123L),
multicastEvent(false, StringEventListener.class, createGenericTestEvent(123L),
getGenericApplicationEventType("longEvent"));
}
@Test // Unfortunate - this should work as well
public void multicastGenericEventWildcardSubType() {
multicastEvent(false, StringEventListener.class, createGenericEvent("test"),
multicastEvent(false, StringEventListener.class, createGenericTestEvent("test"),
getGenericApplicationEventType("wildcardEvent"));
}

View File

@@ -0,0 +1,345 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.Order;
import org.springframework.util.ReflectionUtils;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Stephane Nicoll
*/
public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEventListenerTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final SampleEvents sampleEvents = spy(new SampleEvents());
private final ApplicationContext context = mock(ApplicationContext.class);
@Test
public void rawListener() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleRaw", ApplicationEvent.class);
supportsEventType(true, method, getGenericApplicationEventType("applicationEvent"));
}
@Test
public void rawListenerWithGenericEvent() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleRaw", ApplicationEvent.class);
supportsEventType(true, method, getGenericApplicationEventType("stringEvent"));
}
@Test
public void genericListener() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
supportsEventType(true, method, getGenericApplicationEventType("stringEvent"));
}
@Test
public void genericListenerWrongParameterizedType() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
supportsEventType(false, method, getGenericApplicationEventType("longEvent"));
}
@Test
public void listenerWithPayloadAndGenericInformation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(true, method, createGenericEventType(String.class));
}
@Test
public void listenerWithInvalidPayloadAndGenericInformation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(false, method, createGenericEventType(Integer.class));
}
@Test
public void listenerWithPayloadTypeErasure() { // Always accept such event when the type is unknown
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(true, method, ResolvableType.forClass(PayloadApplicationEvent.class));
}
@Test
public void listenerWithSubTypeSeveralGenerics() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(true, method, ResolvableType.forClass(PayloadTestEvent.class));
}
@Test
public void listenerWithSubTypeSeveralGenericsResolved() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(true, method, ResolvableType.forClass(PayloadStringTestEvent.class));
}
@Test
public void listenerWithTooManyParameters() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"tooManyParameters", String.class, String.class);
thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@Test
public void listenerWithNoParameter() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"noParameter");
thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@Test
public void defaultOrder() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertEquals(0, adapter.getOrder());
}
@Test
public void specifiedOrder() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleRaw", ApplicationEvent.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertEquals(42, adapter.getOrder());
}
@Test
public void invokeListener() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleGenericString(event);
}
@Test
public void invokeListenerRuntimeException() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"generateRuntimeException", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("fail");
thrown.expect(IllegalStateException.class);
thrown.expectMessage("Test exception");
thrown.expectCause(is(isNull(Throwable.class)));
invokeListener(method, event);
}
@Test
public void invokeListenerCheckedException() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"generateCheckedException", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("fail");
thrown.expect(UndeclaredThrowableException.class);
thrown.expectCause(is(instanceOf(IOException.class)));
invokeListener(method, event);
}
@Test
public void invokeListenerInvalidProxy() {
Object target = new InvalidProxyTestBean();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addInterface(SimpleService.class);
Object bean = proxyFactory.getProxy(getClass().getClassLoader());
Method method = ReflectionUtils.findMethod(InvalidProxyTestBean.class, "handleIt2", ApplicationEvent.class);
StaticApplicationListenerMethodAdapter listener =
new StaticApplicationListenerMethodAdapter(method, bean);
thrown.expect(IllegalStateException.class);
thrown.expectMessage("handleIt2");
listener.onApplicationEvent(createGenericTestEvent("test"));
}
@Test
public void invokeListenerWithPayload() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
PayloadApplicationEvent<String> event = new PayloadApplicationEvent<>(this, "test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleString("test");
}
@Test
public void invokeListenerWithPayloadWrongType() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
PayloadApplicationEvent<Long> event = new PayloadApplicationEvent<>(this, 123L);
invokeListener(method, event);
verify(this.sampleEvents, never()).handleString(anyString());
}
@Test
public void beanInstanceRetrievedAtEveryInvocation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
when(this.context.getBean("testBean")).thenReturn(this.sampleEvents);
ApplicationListenerMethodAdapter listener = new ApplicationListenerMethodAdapter(
"testBean", GenericTestEvent.class, method);
listener.init(this.context, new EventExpressionEvaluator());
GenericTestEvent<String> event = createGenericTestEvent("test");
listener.onApplicationEvent(event);
verify(this.sampleEvents, times(1)).handleGenericString(event);
verify(this.context, times(1)).getBean("testBean");
listener.onApplicationEvent(event);
verify(this.sampleEvents, times(2)).handleGenericString(event);
verify(this.context, times(2)).getBean("testBean");
}
private void supportsEventType(boolean match, Method method, ResolvableType eventType) {
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertEquals("Wrong match for event '" + eventType + "' on " + method,
match, adapter.supportsEventType(eventType));
}
private void invokeListener(Method method, ApplicationEvent event) {
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
adapter.onApplicationEvent(event);
}
private ApplicationListenerMethodAdapter createTestInstance(Method method) {
return new StaticApplicationListenerMethodAdapter(method, this.sampleEvents);
}
private ResolvableType createGenericEventType(Class<?> payloadType) {
return ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, payloadType);
}
private static class StaticApplicationListenerMethodAdapter
extends ApplicationListenerMethodAdapter {
private final Object targetBean;
public StaticApplicationListenerMethodAdapter(Method method, Object targetBean) {
super("unused", targetBean.getClass(), method);
this.targetBean = targetBean;
}
@Override
public Object getTargetBean() {
return targetBean;
}
}
private static class SampleEvents {
@EventListener
@Order(42)
public void handleRaw(ApplicationEvent event) {
}
@EventListener
public void handleGenericString(GenericTestEvent<String> event) {
}
@EventListener
public void handleString(String payload) {
}
@EventListener
public void tooManyParameters(String event, String whatIsThis) {
}
@EventListener
public void noParameter() {
}
@EventListener
public void generateRuntimeException(GenericTestEvent<String> event) {
if ("fail".equals(event.getPayload())) {
throw new IllegalStateException("Test exception");
}
}
@EventListener
public void generateCheckedException(GenericTestEvent<String> event) throws IOException {
if ("fail".equals(event.getPayload())) {
throw new IOException("Test exception");
}
}
}
interface SimpleService {
void handleIt(ApplicationEvent event);
}
static class InvalidProxyTestBean implements SimpleService {
@Override
public void handleIt(ApplicationEvent event) {
}
@EventListener
public void handleIt2(ApplicationEvent event) {
}
}
@SuppressWarnings({"unused", "serial"})
static class PayloadTestEvent<V, T> extends PayloadApplicationEvent<T> {
private final V something;
public PayloadTestEvent(Object source, T payload, V something) {
super(source, payload);
this.something = something;
}
}
@SuppressWarnings({"unused", "serial"})
static class PayloadStringTestEvent extends PayloadTestEvent<Long, String> {
public PayloadStringTestEvent(Object source, String payload, Long something) {
super(source, payload, something);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.TestListener;
import org.springframework.context.event.test.TestEvent;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.tests.sample.beans.ITestBean;
import org.springframework.tests.sample.beans.TestBean;
@@ -116,15 +117,6 @@ public class EventPublicationInterceptorTests {
}
@SuppressWarnings("serial")
public static class TestEvent extends ApplicationEvent {
public TestEvent(Object source) {
super(source);
}
}
@SuppressWarnings("serial")
public static final class TestEventWithNoValidOneArgObjectCtor extends ApplicationEvent {

View File

@@ -54,7 +54,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test // Demonstrates we can't inject that event because the generic type is lost
public void genericListenerStrictTypeTypeErasure() {
GenericApplicationEvent<String> stringEvent = createGenericEvent("test");
GenericTestEvent<String> stringEvent = createGenericTestEvent("test");
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
supportsEventType(false, StringEventListener.class, eventType);
}
@@ -62,7 +62,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test // But it works if we specify the type properly
public void genericListenerStrictTypeAndResolvableType() {
ResolvableType eventType = ResolvableType
.forClassWithGenerics(GenericApplicationEvent.class, String.class);
.forClassWithGenerics(GenericTestEvent.class, String.class);
supportsEventType(true, StringEventListener.class, eventType);
}
@@ -87,7 +87,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test
public void genericListenerStrictTypeNotMatchTypeErasure() {
GenericApplicationEvent<Long> longEvent = createGenericEvent(123L);
GenericTestEvent<Long> longEvent = createGenericTestEvent(123L);
ResolvableType eventType = ResolvableType.forType(longEvent.getClass());
supportsEventType(false, StringEventListener.class, eventType);
}
@@ -118,7 +118,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test // Demonstrates we cant inject that event because the listener has a wildcard
public void genericListenerWildcardTypeTypeErasure() {
GenericApplicationEvent<String> stringEvent = createGenericEvent("test");
GenericTestEvent<String> stringEvent = createGenericTestEvent("test");
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
supportsEventType(true, GenericEventListener.class, eventType);
}
@@ -131,7 +131,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test // Demonstrates we cant inject that event because the listener has a raw type
public void genericListenerRawTypeTypeErasure() {
GenericApplicationEvent<String> stringEvent = createGenericEvent("test");
GenericTestEvent<String> stringEvent = createGenericTestEvent("test");
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
supportsEventType(true, RawApplicationListener.class, eventType);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
import java.util.UUID;
/**
* @author Stephane Nicoll
*/
public abstract class AbstractIdentifiable implements Identifiable {
private final String id;
public AbstractIdentifiable() {
this.id = UUID.randomUUID().toString();
}
@Override
public String getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractIdentifiable that = (AbstractIdentifiable) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
/**
* @author Stephane Nicoll
*/
@SuppressWarnings("serial")
public class AnotherTestEvent extends IdentifiableApplicationEvent {
public final String msg;
public AnotherTestEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
/**
* Test utility to collect and assert events.
*
* @author Stephane Nicoll
*/
@Component
public class EventCollector {
private final MultiValueMap<String, Object> content = new LinkedMultiValueMap<>();
/**
* Register an event for the specified listener.
*/
public void addEvent(Identifiable listener, Object event) {
this.content.add(listener.getId(), event);
}
/**
* Return the events that the specified listener has received. The list of events
* is ordered according to their reception order.
*/
public List<Object> getEvents(Identifiable listener) {
return this.content.get(listener.getId());
}
/**
* Assert that the listener identified by the specified id has not received any event.
*/
public void assertNoEventReceived(String listenerId) {
List<Object> events = content.getOrDefault(listenerId, Collections.emptyList());
assertEquals("Expected no events but got " + events, 0, events.size());
}
/**
* Assert that the specified listener has not received any event.
*/
public void assertNoEventReceived(Identifiable listener) {
assertNoEventReceived(listener.getId());
}
/**
* Assert that the listener identified by the specified id has received the
* specified events, in that specific order.
*/
public void assertEvent(String listenerId, Object... events) {
List<Object> actual = content.getOrDefault(listenerId, Collections.emptyList());
assertEquals("wrong number of events", events.length, actual.size());
for (int i = 0; i < events.length; i++) {
assertEquals("Wrong event at index " + i, events[i], actual.get(i));
}
}
/**
* Assert that the specified listener has received the specified events, in
* that specific order.
*/
public void assertEvent(Identifiable listener, Object... events) {
assertEvent(listener.getId(), events);
}
/**
* Assert the number of events received by this instance. Checks that
* unexpected events have not been received. If an event is handled by
* several listeners, each instance will be registered.
*/
public void assertTotalEventsCount(int number) {
int actual = 0;
for (Map.Entry<String, List<Object>> entry : this.content.entrySet()) {
actual += entry.getValue().size();
}
assertEquals("Wrong number of total events (" + this.content.size() + ") " +
"registered listener(s)", number, actual);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
/**
* A simple marker interface used to identify an event or an event listener
*
* @author Stephane Nicoll
*/
public interface Identifiable {
/**
* Return a unique global id used to identify this instance.
*/
String getId();
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
import java.util.UUID;
import org.springframework.context.ApplicationEvent;
/**
* A basic test event that can be uniquely identified easily.
*
* @author Stephane Nicoll
*/
@SuppressWarnings("serial")
public abstract class IdentifiableApplicationEvent extends ApplicationEvent implements Identifiable {
private final String id;
protected IdentifiableApplicationEvent(Object source, String id) {
super(source);
this.id = id;
}
protected IdentifiableApplicationEvent(Object source) {
this(source, UUID.randomUUID().toString());
}
protected IdentifiableApplicationEvent() {
this(new Object());
}
@Override
public String getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IdentifiableApplicationEvent that = (IdentifiableApplicationEvent) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
/**
* @author Stephane Nicoll
*/
@SuppressWarnings("serial")
public class TestEvent extends IdentifiableApplicationEvent {
public final String msg;
public TestEvent(Object source, String id, String msg) {
super(source, id);
this.msg = msg;
}
public TestEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public TestEvent(Object source) {
this(source, "test");
}
public TestEvent() {
this(new Object());
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.expression;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.util.ReflectionUtils;
import static org.junit.Assert.*;
/**
* @author Stephane Nicoll
*/
public class MethodBasedEvaluationContextTest {
private final ParameterNameDiscoverer paramDiscover = new DefaultParameterNameDiscoverer();
@Test
public void simpleArguments() {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello",
String.class, Boolean.class);
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {"test", true});
assertEquals("test", context.lookupVariable("a0"));
assertEquals("test", context.lookupVariable("p0"));
assertEquals("test", context.lookupVariable("foo"));
assertEquals(true, context.lookupVariable("a1"));
assertEquals(true, context.lookupVariable("p1"));
assertEquals(true, context.lookupVariable("flag"));
assertNull(context.lookupVariable("a2"));
}
@Test
public void nullArgument() {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello",
String.class, Boolean.class);
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, null});
assertNull(context.lookupVariable("a0"));
assertNull(context.lookupVariable("p0"));
}
private MethodBasedEvaluationContext createEvaluationContext(Method method, Object[] args) {
return new MethodBasedEvaluationContext(this, method, args, this.paramDiscover);
}
@SuppressWarnings("unused")
private static class SampleMethods {
private void hello(String foo, Boolean flag) {
}
}
}