diff --git a/spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java b/spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java index 14dd368759f169d3eeadd80574e8f444bd357e3c..b314cf5aa2f950c21aa539336f4a4c0c6b3203e2 100644 --- a/spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java +++ b/spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2005 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. @@ -30,9 +30,9 @@ package org.springframework.context; public interface ApplicationEventPublisher { /** - * Notify all listeners registered with this application of an application - * event. Events may be framework events (such as RequestHandledEvent) - * or application-specific events. + * Notify all matching listeners registered with this + * application of an application event. Events may be framework events + * (such as RequestHandledEvent) or application-specific events. * @param event the event to publish * @see org.springframework.web.context.support.RequestHandledEvent */ diff --git a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java index 850e4df4cffb7fa4a79d15e38c674fbebf8cdb39..7fc13089f6b50aa4a02062f284c8300d7c86f94a 100644 --- a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -31,6 +31,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.OrderComparator; +import org.springframework.core.ResolvableType; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -49,8 +50,9 @@ import org.springframework.util.ObjectUtils; * Alternative implementations could be more sophisticated in those respects. * * @author Juergen Hoeller + * @author Stephane Nicoll * @since 1.2.3 - * @see #getApplicationListeners(ApplicationEvent) + * @see #getApplicationListeners(ApplicationEvent, ResolvableType) * @see SimpleApplicationEventMulticaster */ public abstract class AbstractApplicationEventMulticaster @@ -145,13 +147,16 @@ public abstract class AbstractApplicationEventMulticaster * event type. Non-matching listeners get excluded early. * @param event the event to be propagated. Allows for excluding * non-matching listeners early, based on cached matching information. + * @param eventType the event type * @return a Collection of ApplicationListeners * @see org.springframework.context.ApplicationListener */ - protected Collection> getApplicationListeners(ApplicationEvent event) { + protected Collection> getApplicationListeners( + ApplicationEvent event, ResolvableType eventType) { + Object source = event.getSource(); Class sourceType = (source != null ? source.getClass() : null); - ListenerCacheKey cacheKey = new ListenerCacheKey(event.getClass(), sourceType); + ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); // Quick check for existing entry on ConcurrentHashMap... ListenerRetriever retriever = this.retrieverCache.get(cacheKey); @@ -169,26 +174,28 @@ public abstract class AbstractApplicationEventMulticaster return retriever.getApplicationListeners(); } retriever = new ListenerRetriever(true); - Collection> listeners = retrieveApplicationListeners(event, sourceType, retriever); + Collection> listeners = + retrieveApplicationListeners(event, eventType, sourceType, retriever); this.retrieverCache.put(cacheKey, retriever); return listeners; } } else { // No ListenerRetriever caching -> no synchronization necessary - return retrieveApplicationListeners(event, sourceType, null); + return retrieveApplicationListeners(event, eventType, sourceType, null); } } /** * Actually retrieve the application listeners for the given event and source type. * @param event the application event + * @param eventType the event type * @param sourceType the event source type * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes) * @return the pre-filtered list of application listeners for the given event and source type */ private Collection> retrieveApplicationListeners( - ApplicationEvent event, Class sourceType, ListenerRetriever retriever) { + ApplicationEvent event, ResolvableType eventType, Class sourceType, ListenerRetriever retriever) { LinkedList> allListeners = new LinkedList>(); Set> listeners; @@ -198,7 +205,7 @@ public abstract class AbstractApplicationEventMulticaster listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans); } for (ApplicationListener listener : listeners) { - if (supportsEvent(listener, event.getClass(), sourceType)) { + if (supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { retriever.applicationListeners.add(listener); } @@ -210,10 +217,10 @@ public abstract class AbstractApplicationEventMulticaster for (String listenerBeanName : listenerBeans) { try { Class listenerType = beanFactory.getType(listenerBeanName); - if (listenerType == null || supportsEvent(listenerType, event)) { + if (listenerType == null || supportsEvent(listenerType, eventType)) { ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); - if (!allListeners.contains(listener) && supportsEvent(listener, event.getClass(), sourceType)) { + if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { retriever.applicationListenerBeans.add(listenerBeanName); } @@ -236,26 +243,27 @@ public abstract class AbstractApplicationEventMulticaster * type before trying to instantiate it. *

If this method returns {@code true} for a given listener as a first pass, * the listener instance will get retrieved and fully evaluated through a - * {@link #supportsEvent(ApplicationListener, Class, Class)} call afterwards. + * {@link #supportsEvent(ApplicationListener,ResolvableType, Class)} call afterwards. * @param listenerType the listener's type as determined by the BeanFactory - * @param event the event to check + * @param eventType the event type to check * @return whether the given listener should be included in the candidates * for the given event type */ - protected boolean supportsEvent(Class listenerType, ApplicationEvent event) { - if (SmartApplicationListener.class.isAssignableFrom(listenerType)) { + protected boolean supportsEvent(Class listenerType, ResolvableType eventType) { + if (GenericApplicationListener.class.isAssignableFrom(listenerType) + || SmartApplicationListener.class.isAssignableFrom(listenerType)) { return true; } - Class declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType); - return (declaredEventType == null || declaredEventType.isInstance(event)); + ResolvableType declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType); + return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType)); } /** * Determine whether the given listener supports the given event. *

The default implementation detects the {@link SmartApplicationListener} - * interface. In case of a standard {@link ApplicationListener}, a - * {@link GenericApplicationListenerAdapter} will be used to introspect - * the generically declared type of the target listener. + * and {@link GenericApplicationListener} interfaces. In case of a standard + * {@link ApplicationListener}, a {@link GenericApplicationListenerAdapter} + * will be used to introspect the generically declared type of the target listener. * @param listener the target listener to check * @param eventType the event type to check against * @param sourceType the source type to check against @@ -263,10 +271,10 @@ public abstract class AbstractApplicationEventMulticaster * for the given event type */ protected boolean supportsEvent(ApplicationListener listener, - Class eventType, Class sourceType) { + ResolvableType eventType, Class sourceType) { - SmartApplicationListener smartListener = (listener instanceof SmartApplicationListener ? - (SmartApplicationListener) listener : new GenericApplicationListenerAdapter(listener)); + GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ? + (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener)); return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType)); } @@ -276,11 +284,11 @@ public abstract class AbstractApplicationEventMulticaster */ private static class ListenerCacheKey { - private final Class eventType; + private final ResolvableType eventType; private final Class sourceType; - public ListenerCacheKey(Class eventType, Class sourceType) { + public ListenerCacheKey(ResolvableType eventType, Class sourceType) { this.eventType = eventType; this.sourceType = sourceType; } diff --git a/spring-context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java index 03bd1ceefe890d729ae95f16bd0effa872a7973f..9805323040068b9bb88f7417311ac609539d938a 100644 --- a/spring-context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 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. @@ -18,6 +18,7 @@ package org.springframework.context.event; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; +import org.springframework.core.ResolvableType; /** * Interface to be implemented by objects that can manage a number of @@ -29,6 +30,7 @@ import org.springframework.context.ApplicationListener; * * @author Rod Johnson * @author Juergen Hoeller + * @author Stephane Nicoll */ public interface ApplicationEventMulticaster { @@ -65,8 +67,19 @@ public interface ApplicationEventMulticaster { /** * Multicast the given application event to appropriate listeners. + *

Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)} + * if possible as it provides a better support for generics-based events. * @param event the event to multicast */ void multicastEvent(ApplicationEvent event); + /** + * Multicast the given application event to appropriate listeners. + *

If the {@code eventType} is {@code null}, a default type is built + * based on the {@code event} instance. + * @param event the event to multicast + * @param eventType the type of event (can be null) + */ + void multicastEvent(ApplicationEvent event, ResolvableType eventType); + } diff --git a/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListener.java b/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListener.java new file mode 100644 index 0000000000000000000000000000000000000000..cfe7f457a948a38b19335456145b946abfe82c38 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListener.java @@ -0,0 +1,46 @@ +/* + * 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 org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.ResolvableType; + +/** + * Extended variant of the standard {@link ApplicationListener} interface, + * exposing further metadata such as the supported event type. + * + *

As of Spring Framework 4.2, supersedes {@link SmartApplicationListener} with + * proper handling of generics-based event. + * + * @author Stephane Nicoll + * @since 4.2 + */ +public interface GenericApplicationListener extends ApplicationListener, Ordered { + + /** + * Determine whether this listener actually supports the given event type. + */ + boolean supportsEventType(ResolvableType eventType); + + /** + * Determine whether this listener actually supports the given source type. + */ + boolean supportsSourceType(Class sourceType); + +} diff --git a/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java b/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java index 0d46b5980ae5aaaae0acb44101715f3ebb675532..9362ac78755e9a4439c7f1eb400f2f5883af727b 100644 --- a/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java +++ b/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -19,22 +19,24 @@ package org.springframework.context.event; import org.springframework.aop.support.AopUtils; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; -import org.springframework.core.GenericTypeResolver; import org.springframework.core.Ordered; +import org.springframework.core.ResolvableType; import org.springframework.util.Assert; /** - * {@link SmartApplicationListener} adapter that determines supported event types + * {@link GenericApplicationListener} adapter that determines supported event types * through introspecting the generically declared type of the target listener. * * @author Juergen Hoeller + * @author Stephane Nicoll * @since 3.0 * @see org.springframework.context.ApplicationListener#onApplicationEvent */ -public class GenericApplicationListenerAdapter implements SmartApplicationListener { +public class GenericApplicationListenerAdapter implements GenericApplicationListener { private final ApplicationListener delegate; + private final ResolvableType declaredEventType; /** * Create a new GenericApplicationListener for the given delegate. @@ -44,6 +46,7 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen public GenericApplicationListenerAdapter(ApplicationListener delegate) { Assert.notNull(delegate, "Delegate listener must not be null"); this.delegate = (ApplicationListener) delegate; + this.declaredEventType = resolveDeclaredEventType(this.delegate); } @@ -53,20 +56,25 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen } @Override - public boolean supportsEventType(Class eventType) { - Class declaredEventType = resolveDeclaredEventType(this.delegate.getClass()); - if (declaredEventType == null || declaredEventType.equals(ApplicationEvent.class)) { - Class targetClass = AopUtils.getTargetClass(this.delegate); - if (targetClass != this.delegate.getClass()) { - declaredEventType = resolveDeclaredEventType(targetClass); - } + @SuppressWarnings("unchecked") + public boolean supportsEventType(ResolvableType eventType) { + if (this.delegate instanceof SmartApplicationListener) { + Class eventClass = (Class) eventType.getRawClass(); + return ((SmartApplicationListener) this.delegate).supportsEventType(eventClass); + } + else { + return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType)); } - return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType)); } @Override public boolean supportsSourceType(Class sourceType) { - return true; + if (this.delegate instanceof SmartApplicationListener) { + return ((SmartApplicationListener) this.delegate).supportsSourceType(sourceType); + } + else { + return true; + } } @Override @@ -74,9 +82,25 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen return (this.delegate instanceof Ordered ? ((Ordered) this.delegate).getOrder() : Ordered.LOWEST_PRECEDENCE); } + static ResolvableType resolveDeclaredEventType(Class listenerType) { + ResolvableType resolvableType = ResolvableType.forClass(listenerType).as(ApplicationListener.class); + if (resolvableType == null || !resolvableType.hasGenerics()) { + return null; + } + return resolvableType.getGeneric(); + } + + private static ResolvableType resolveDeclaredEventType(ApplicationListener listener) { + ResolvableType declaredEventType = resolveDeclaredEventType(listener.getClass()); + if (declaredEventType == null || declaredEventType.isAssignableFrom( + ResolvableType.forClass(ApplicationEvent.class))) { - static Class resolveDeclaredEventType(Class listenerType) { - return GenericTypeResolver.resolveTypeArgument(listenerType, ApplicationListener.class); + Class targetClass = AopUtils.getTargetClass(listener); + if (targetClass != listener.getClass()) { + declaredEventType = resolveDeclaredEventType(targetClass); + } + } + return declaredEventType; } } diff --git a/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java index d044aaf94658ab1256582789d82ac2947d755776..28587f21f4964f94e19dc2589a3d53cb7f7b186d 100644 --- a/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -21,6 +21,7 @@ import java.util.concurrent.Executor; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; +import org.springframework.core.ResolvableType; import org.springframework.util.ErrorHandler; /** @@ -38,6 +39,7 @@ import org.springframework.util.ErrorHandler; * * @author Rod Johnson * @author Juergen Hoeller + * @author Stephane Nicoll * @see #setTaskExecutor */ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { @@ -113,8 +115,14 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM @Override - public void multicastEvent(final ApplicationEvent event) { - for (final ApplicationListener listener : getApplicationListeners(event)) { + public void multicastEvent(ApplicationEvent event) { + multicastEvent(event, resolveDefaultEventType(event)); + } + + @Override + public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) { + ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); + for (final ApplicationListener listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(new Runnable() { @@ -130,6 +138,10 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM } } + private ResolvableType resolveDefaultEventType(ApplicationEvent event) { + return ResolvableType.forType(event.getClass()); + } + /** * Invoke the given listener with the given event. * @param listener the ApplicationListener to invoke diff --git a/spring-context/src/main/java/org/springframework/context/event/SmartApplicationListener.java b/spring-context/src/main/java/org/springframework/context/event/SmartApplicationListener.java index 9fe22adaf1e5908bda0d139c20013122604ead55..5d509207541e02ad6cc5a992b6ea3d973037590d 100644 --- a/spring-context/src/main/java/org/springframework/context/event/SmartApplicationListener.java +++ b/spring-context/src/main/java/org/springframework/context/event/SmartApplicationListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 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. @@ -24,8 +24,13 @@ import org.springframework.core.Ordered; * Extended variant of the standard {@link ApplicationListener} interface, * exposing further metadata such as the supported event type. * + *

Users are strongly advised to use the {@link GenericApplicationListener} + * interface instead as it provides an improved detection of generics-based + * event types. + * * @author Juergen Hoeller * @since 3.0 + * @see GenericApplicationListener */ public interface SmartApplicationListener extends ApplicationListener, Ordered { diff --git a/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java b/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java index 93bf92f756bab1f3834662aa6a84aa836d74ee5b..6143b48483115e95c2e2ccc5e03981972c931094 100644 --- a/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java +++ b/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java @@ -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. @@ -19,6 +19,7 @@ package org.springframework.context.event; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; +import org.springframework.core.ResolvableType; /** * {@link org.springframework.context.ApplicationListener} decorator that filters @@ -29,13 +30,14 @@ import org.springframework.core.Ordered; * method instead of specifying a delegate listener. * * @author Juergen Hoeller + * @author Stephane Nicoll * @since 2.0.5 */ -public class SourceFilteringListener implements SmartApplicationListener { +public class SourceFilteringListener implements GenericApplicationListener { private final Object source; - private SmartApplicationListener delegate; + private GenericApplicationListener delegate; /** @@ -47,8 +49,8 @@ public class SourceFilteringListener implements SmartApplicationListener { */ public SourceFilteringListener(Object source, ApplicationListener delegate) { this.source = source; - this.delegate = (delegate instanceof SmartApplicationListener ? - (SmartApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate)); + this.delegate = (delegate instanceof GenericApplicationListener ? + (GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate)); } /** @@ -71,7 +73,7 @@ public class SourceFilteringListener implements SmartApplicationListener { } @Override - public boolean supportsEventType(Class eventType) { + public boolean supportsEventType(ResolvableType eventType) { return (this.delegate == null || this.delegate.supportsEventType(eventType)); } diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 75e5e3d873f750a70ee3187eea8dd18b173f1b64..aac94c976df38c1d906d50f40dcfec50d4fc5747 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -63,6 +63,7 @@ import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.context.expression.StandardBeanExpressionResolver; import org.springframework.context.weaving.LoadTimeWeaverAware; import org.springframework.context.weaving.LoadTimeWeaverAwareProcessor; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -107,6 +108,7 @@ import org.springframework.util.ObjectUtils; * @author Rod Johnson * @author Juergen Hoeller * @author Mark Fisher + * @author Stephane Nicoll * @since January 21, 2001 * @see #refreshBeanFactory * @see #getBeanFactory @@ -324,13 +326,22 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader */ @Override public void publishEvent(ApplicationEvent event) { + publishEvent(event, null); + } + + protected void publishEvent(ApplicationEvent event, ResolvableType eventType) { Assert.notNull(event, "Event must not be null"); if (logger.isTraceEnabled()) { logger.trace("Publishing event in " + getDisplayName() + ": " + event); } - getApplicationEventMulticaster().multicastEvent(event); + getApplicationEventMulticaster().multicastEvent(event, eventType); if (this.parent != null) { - this.parent.publishEvent(event); + if (this.parent instanceof AbstractApplicationContext) { + ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); + } + else { + this.parent.publishEvent(event); + } } } diff --git a/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java b/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java new file mode 100644 index 0000000000000000000000000000000000000000..98dd3ea1cad34b4e753b5536c0ed5d32fa5a881b --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java @@ -0,0 +1,129 @@ +/* + * 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 org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.ResolvableType; + +/** + * @author Stephane Nicoll + */ +@SuppressWarnings("serial") +public abstract class AbstractApplicationEventListenerTests { + + protected ResolvableType getGenericApplicationEventType(String fieldName) { + try { + return ResolvableType.forField(GenericApplicationEvents.class.getField(fieldName)); + } + catch (NoSuchFieldException e) { + throw new IllegalStateException("No such field on Events '" + fieldName + "'"); + } + } + + protected static class GenericApplicationEvent + extends ApplicationEvent { + + private final T payload; + + public GenericApplicationEvent(Object source, T payload) { + super(source); + this.payload = payload; + } + + public T getPayload() { + return payload; + } + + } + + protected static class StringEvent extends GenericApplicationEvent { + + public StringEvent(Object source, String payload) { + super(source, payload); + } + } + + protected static class LongEvent extends GenericApplicationEvent { + + public LongEvent(Object source, Long payload) { + super(source, payload); + } + } + + protected GenericApplicationEvent createGenericEvent(T payload) { + return new GenericApplicationEvent<>(this, payload); + } + + + static class GenericEventListener implements ApplicationListener> { + @Override + public void onApplicationEvent(GenericApplicationEvent event) { + + } + } + + static class ObjectEventListener implements ApplicationListener> { + @Override + public void onApplicationEvent(GenericApplicationEvent event) { + + } + } + + static class UpperBoundEventListener + implements ApplicationListener> { + + @Override + public void onApplicationEvent(GenericApplicationEvent event) { + + } + } + + static class StringEventListener implements ApplicationListener> { + + @Override + public void onApplicationEvent(GenericApplicationEvent event) { + + } + } + + static class RawApplicationListener implements ApplicationListener { + @Override + public void onApplicationEvent(ApplicationEvent event) { + + } + } + + + @SuppressWarnings("unused") + static class GenericApplicationEvents { + + public GenericApplicationEvent wildcardEvent; + + public GenericApplicationEvent stringEvent; + + public GenericApplicationEvent longEvent; + + public GenericApplicationEvent illegalStateExceptionEvent; + + public GenericApplicationEvent ioExceptionEvent; + + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index 16e635a60cdcd88e8fd52f6f1861814f0bb940d3..8c214bb7ca1b38117769ad320a05affc09b16e8e 100644 --- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -34,6 +34,7 @@ import org.springframework.context.BeanThatListens; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.Ordered; +import org.springframework.core.ResolvableType; import org.springframework.scheduling.support.TaskUtils; import org.springframework.tests.sample.beans.TestBean; @@ -45,20 +46,59 @@ import static org.mockito.BDDMockito.*; * * @author Alef Arendsen * @author Rick Evans + * @author Stephane Nicoll */ -public class ApplicationContextEventTests { +public class ApplicationContextEventTests extends AbstractApplicationEventListenerTests { @Test - public void simpleApplicationEventMulticaster() { - @SuppressWarnings("unchecked") - ApplicationListener listener = mock(ApplicationListener.class); - ApplicationEvent evt = new ContextClosedEvent(new StaticApplicationContext()); + public void multicastSimpleEvent() { + multicastEvent(true, ApplicationListener.class, + new ContextClosedEvent(new StaticApplicationContext()), null); + } + @Test + public void multicastGenericEvent() { + multicastEvent(true, StringEventListener.class, createGenericEvent("test"), + getGenericApplicationEventType("stringEvent")); + } + + @Test + public void multicastGenericEventWrongType() { + multicastEvent(false, StringEventListener.class, createGenericEvent(123L), + getGenericApplicationEventType("longEvent")); + } + + @Test // Unfortunate - this should work as well + public void multicastGenericEventWildcardSubType() { + multicastEvent(false, StringEventListener.class, createGenericEvent("test"), + getGenericApplicationEventType("wildcardEvent")); + } + + @Test + public void multicastConcreteTypeGenericListener() { + multicastEvent(true, StringEventListener.class, new StringEvent(this, "test"), null); + } + + @Test + public void multicastConcreteWrongTypeGenericListener() { + multicastEvent(false, StringEventListener.class, new LongEvent(this, 123L), null); + } + + private void multicastEvent(boolean match, Class listenerType, + ApplicationEvent event, ResolvableType eventType) { + @SuppressWarnings("unchecked") + ApplicationListener listener = + (ApplicationListener) mock(listenerType); SimpleApplicationEventMulticaster smc = new SimpleApplicationEventMulticaster(); smc.addApplicationListener(listener); - smc.multicastEvent(evt); - verify(listener).onApplicationEvent(evt); + if (eventType != null) { + smc.multicastEvent(event, eventType); + } else { + smc.multicastEvent(event); + } + int invocation = match ? 1 : 0; + verify(listener, times(invocation)).onApplicationEvent(event); } @Test diff --git a/spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java b/spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java new file mode 100644 index 0000000000000000000000000000000000000000..744ecf49e72f2c04fc052d9c39e4bccb92c3ad19 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java @@ -0,0 +1,148 @@ +/* + * 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 org.junit.Test; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.ResolvableType; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Stephane Nicoll + */ +public class GenericApplicationListenerAdapterTests extends AbstractApplicationEventListenerTests { + + @Test + public void supportsEventTypeWithSmartApplicationListener() { + SmartApplicationListener smartListener = mock(SmartApplicationListener.class); + GenericApplicationListenerAdapter listener = new GenericApplicationListenerAdapter(smartListener); + ResolvableType type = ResolvableType.forClass(ApplicationEvent.class); + listener.supportsEventType(type); + verify(smartListener, times(1)).supportsEventType(ApplicationEvent.class); + } + + @Test + public void supportsSourceTypeWithSmartApplicationListener() { + SmartApplicationListener smartListener = mock(SmartApplicationListener.class); + GenericApplicationListenerAdapter listener = new GenericApplicationListenerAdapter(smartListener); + listener.supportsSourceType(Object.class); + verify(smartListener, times(1)).supportsSourceType(Object.class); + } + + @Test + public void genericListenerStrictType() { + supportsEventType(true, StringEventListener.class, getGenericApplicationEventType("stringEvent")); + } + + @Test // Demonstrates we can't inject that event because the generic type is lost + public void genericListenerStrictTypeTypeErasure() { + GenericApplicationEvent stringEvent = createGenericEvent("test"); + ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); + supportsEventType(false, StringEventListener.class, eventType); + } + + @Test // But it works if we specify the type properly + public void genericListenerStrictTypeAndResolvableType() { + ResolvableType eventType = ResolvableType + .forClassWithGenerics(GenericApplicationEvent.class, String.class); + supportsEventType(true, StringEventListener.class, eventType); + } + + @Test // Demonstrates it works if we actually use the subtype + public void genericListenerStrictTypeEventSubType() { + StringEvent stringEvent = new StringEvent(this, "test"); + ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); + supportsEventType(true, StringEventListener.class, eventType); + } + + @Test + public void genericListenerStrictTypeNotMatching() { + supportsEventType(false, StringEventListener.class, getGenericApplicationEventType("longEvent")); + } + + @Test + public void genericListenerStrictTypeEventSubTypeNotMatching() { + LongEvent stringEvent = new LongEvent(this, 123L); + ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); + supportsEventType(false, StringEventListener.class, eventType); + } + + @Test + public void genericListenerStrictTypeNotMatchTypeErasure() { + GenericApplicationEvent longEvent = createGenericEvent(123L); + ResolvableType eventType = ResolvableType.forType(longEvent.getClass()); + supportsEventType(false, StringEventListener.class, eventType); + } + + @Test + public void genericListenerStrictTypeSubClass() { + supportsEventType(false, ObjectEventListener.class, + getGenericApplicationEventType("longEvent")); + } + + @Test + public void genericListenerUpperBoundType() { + supportsEventType(true, UpperBoundEventListener.class, + getGenericApplicationEventType("illegalStateExceptionEvent")); + } + + @Test + public void genericListenerUpperBoundTypeNotMatching() throws NoSuchFieldException { + supportsEventType(false, UpperBoundEventListener.class, + getGenericApplicationEventType("ioExceptionEvent")); + } + + @Test + public void genericListenerWildcardType() { + supportsEventType(true, GenericEventListener.class, + getGenericApplicationEventType("stringEvent")); + } + + @Test // Demonstrates we cant inject that event because the listener has a wildcard + public void genericListenerWildcardTypeTypeErasure() { + GenericApplicationEvent stringEvent = createGenericEvent("test"); + ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); + supportsEventType(true, GenericEventListener.class, eventType); + } + + @Test + public void genericListenerRawType() { + supportsEventType(true, RawApplicationListener.class, + getGenericApplicationEventType("stringEvent")); + } + + @Test // Demonstrates we cant inject that event because the listener has a raw type + public void genericListenerRawTypeTypeErasure() { + GenericApplicationEvent stringEvent = createGenericEvent("test"); + ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); + supportsEventType(true, RawApplicationListener.class, eventType); + } + + private void supportsEventType(boolean match, Class listenerType, + ResolvableType eventType) { + + ApplicationListener listener = mock(listenerType); + GenericApplicationListenerAdapter adapter = new GenericApplicationListenerAdapter(listener); + assertEquals("Wrong match for event '" + eventType + "' on " + listenerType.getClass().getName(), + match, adapter.supportsEventType(eventType)); + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/support/StaticApplicationContextMulticasterTests.java b/spring-context/src/test/java/org/springframework/context/support/StaticApplicationContextMulticasterTests.java index f2f74bc35d15db8684e7106e2d5cb42cefec14e7..981b9ef8832510a70ef716485f0123c1bdd7ed1e 100644 --- a/spring-context/src/test/java/org/springframework/context/support/StaticApplicationContextMulticasterTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/StaticApplicationContextMulticasterTests.java @@ -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. @@ -28,6 +28,7 @@ import org.springframework.context.ApplicationEvent; import org.springframework.context.BeanThatListens; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.core.ResolvableType; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.EncodedResource; @@ -92,8 +93,8 @@ public class StaticApplicationContextMulticasterTests extends AbstractApplicatio private static int counter = 0; @Override - public void multicastEvent(ApplicationEvent event) { - super.multicastEvent(event); + public void multicastEvent(ApplicationEvent event, ResolvableType eventType) { + super.multicastEvent(event, eventType); counter++; } }