Browse Source

Support for generics-based events

Update the event publishing infrastructure to support generics-based
events, that is support ApplicationListener implementations that define
a generic event, something like:

public class MyListener
        implements ApplicationListener<GenericEvent<String>> { ... }

This listener should only receive events that are matching the generic
signature, for instance:

public class StringEvent extends GenericEvent<String> { ... }

Note that because of type erasure, publishing an event that defines the
generic type at the instance level will not work. In other words,
publishing "new GenericEvent<String>" will not work as expected as type
erasure will define it as GenericEvent<?>.

To support this feature, use the new GenericApplicationListener that
supersedes SmartApplicationListener to handle generics-based even types via
`supportsEventType` that takes a ResolvableType instance instead of the
simple Class of the event. ApplicationEventMulticaster has an additional
method to multicast an event based on the event and its ResolvableType.

Issue: SPR-8201
pull/723/merge
Stephane Nicoll 10 years ago
parent
commit
6d6422acde
  1. 8
      spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java
  2. 56
      spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java
  3. 15
      spring-context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java
  4. 46
      spring-context/src/main/java/org/springframework/context/event/GenericApplicationListener.java
  5. 54
      spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java
  6. 18
      spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java
  7. 7
      spring-context/src/main/java/org/springframework/context/event/SmartApplicationListener.java
  8. 14
      spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java
  9. 17
      spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
  10. 129
      spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java
  11. 56
      spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java
  12. 148
      spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java
  13. 7
      spring-context/src/test/java/org/springframework/context/support/StaticApplicationContextMulticasterTests.java

8
spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 <strong>matching</strong> 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
*/

56
spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 @@ -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<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event) {
protected Collection<ApplicationListener<?>> 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 @@ -169,26 +174,28 @@ public abstract class AbstractApplicationEventMulticaster
return retriever.getApplicationListeners();
}
retriever = new ListenerRetriever(true);
Collection<ApplicationListener<?>> listeners = retrieveApplicationListeners(event, sourceType, retriever);
Collection<ApplicationListener<?>> 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<ApplicationListener<?>> retrieveApplicationListeners(
ApplicationEvent event, Class<?> sourceType, ListenerRetriever retriever) {
ApplicationEvent event, ResolvableType eventType, Class<?> sourceType, ListenerRetriever retriever) {
LinkedList<ApplicationListener<?>> allListeners = new LinkedList<ApplicationListener<?>>();
Set<ApplicationListener<?>> listeners;
@ -198,7 +205,7 @@ public abstract class AbstractApplicationEventMulticaster @@ -198,7 +205,7 @@ public abstract class AbstractApplicationEventMulticaster
listenerBeans = new LinkedHashSet<String>(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 @@ -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 @@ -236,26 +243,27 @@ public abstract class AbstractApplicationEventMulticaster
* type before trying to instantiate it.
* <p>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.
* <p>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 @@ -263,10 +271,10 @@ public abstract class AbstractApplicationEventMulticaster
* for the given event type
*/
protected boolean supportsEvent(ApplicationListener<?> listener,
Class<? extends ApplicationEvent> 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 @@ -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;
}

15
spring-context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 { @@ -65,8 +67,19 @@ public interface ApplicationEventMulticaster {
/**
* Multicast the given application event to appropriate listeners.
* <p>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.
* <p>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);
}

46
spring-context/src/main/java/org/springframework/context/event/GenericApplicationListener.java

@ -0,0 +1,46 @@ @@ -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.
*
* <p>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<ApplicationEvent>, 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);
}

54
spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java

@ -1,5 +1,5 @@ @@ -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; @@ -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<ApplicationEvent> delegate;
private final ResolvableType declaredEventType;
/**
* Create a new GenericApplicationListener for the given delegate.
@ -44,6 +46,7 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen @@ -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<ApplicationEvent>) delegate;
this.declaredEventType = resolveDeclaredEventType(this.delegate);
}
@ -53,20 +56,25 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen @@ -53,20 +56,25 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> 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<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) 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 @@ -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<ApplicationEvent> 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;
}
}

18
spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 @@ -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 @@ -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

7
spring-context/src/main/java/org/springframework/context/event/SmartApplicationListener.java

@ -1,5 +1,5 @@ @@ -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; @@ -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.
*
* <p>Users are <bold>strongly advised</bold> 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<ApplicationEvent>, Ordered {

14
spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -71,7 +73,7 @@ public class SourceFilteringListener implements SmartApplicationListener {
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
public boolean supportsEventType(ResolvableType eventType) {
return (this.delegate == null || this.delegate.supportsEventType(eventType));
}

17
spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 @@ -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);
}
}
}

129
spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java

@ -0,0 +1,129 @@ @@ -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<T>
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<String> {
public StringEvent(Object source, String payload) {
super(source, payload);
}
}
protected static class LongEvent extends GenericApplicationEvent<Long> {
public LongEvent(Object source, Long payload) {
super(source, payload);
}
}
protected <T> GenericApplicationEvent<T> createGenericEvent(T payload) {
return new GenericApplicationEvent<>(this, payload);
}
static class GenericEventListener implements ApplicationListener<GenericApplicationEvent<?>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<?> event) {
}
}
static class ObjectEventListener implements ApplicationListener<GenericApplicationEvent<Object>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<Object> event) {
}
}
static class UpperBoundEventListener
implements ApplicationListener<GenericApplicationEvent<? extends RuntimeException>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<? extends RuntimeException> event) {
}
}
static class StringEventListener implements ApplicationListener<GenericApplicationEvent<String>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<String> event) {
}
}
static class RawApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
}
}
@SuppressWarnings("unused")
static class GenericApplicationEvents {
public GenericApplicationEvent<?> wildcardEvent;
public GenericApplicationEvent<String> stringEvent;
public GenericApplicationEvent<Long> longEvent;
public GenericApplicationEvent<IllegalStateException> illegalStateExceptionEvent;
public GenericApplicationEvent<IOException> ioExceptionEvent;
}
}

56
spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java

@ -1,5 +1,5 @@ @@ -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; @@ -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.*; @@ -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<ApplicationEvent> 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<ApplicationEvent> listener =
(ApplicationListener<ApplicationEvent>) 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

148
spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java

@ -0,0 +1,148 @@ @@ -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<String> 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<Long> 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<String> 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<String> stringEvent = createGenericEvent("test");
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
supportsEventType(true, RawApplicationListener.class, eventType);
}
private void supportsEventType(boolean match, Class<? extends ApplicationListener> 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));
}
}

7
spring-context/src/test/java/org/springframework/context/support/StaticApplicationContextMulticasterTests.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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++;
}
}

Loading…
Cancel
Save