Skip to content

Commit 6d6422a

Browse files
committed
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
1 parent f84c458 commit 6d6422a

13 files changed

+507
-68
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2005 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,9 +30,9 @@
3030
public interface ApplicationEventPublisher {
3131

3232
/**
33-
* Notify all listeners registered with this application of an application
34-
* event. Events may be framework events (such as RequestHandledEvent)
35-
* or application-specific events.
33+
* Notify all <strong>matching</strong> listeners registered with this
34+
* application of an application event. Events may be framework events
35+
* (such as RequestHandledEvent) or application-specific events.
3636
* @param event the event to publish
3737
* @see org.springframework.web.context.support.RequestHandledEvent
3838
*/

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

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import org.springframework.context.ApplicationEvent;
3232
import org.springframework.context.ApplicationListener;
3333
import org.springframework.core.OrderComparator;
34+
import org.springframework.core.ResolvableType;
3435
import org.springframework.util.ClassUtils;
3536
import org.springframework.util.ObjectUtils;
3637

@@ -49,8 +50,9 @@
4950
* Alternative implementations could be more sophisticated in those respects.
5051
*
5152
* @author Juergen Hoeller
53+
* @author Stephane Nicoll
5254
* @since 1.2.3
53-
* @see #getApplicationListeners(ApplicationEvent)
55+
* @see #getApplicationListeners(ApplicationEvent, ResolvableType)
5456
* @see SimpleApplicationEventMulticaster
5557
*/
5658
public abstract class AbstractApplicationEventMulticaster
@@ -145,13 +147,16 @@ protected Collection<ApplicationListener<?>> getApplicationListeners() {
145147
* event type. Non-matching listeners get excluded early.
146148
* @param event the event to be propagated. Allows for excluding
147149
* non-matching listeners early, based on cached matching information.
150+
* @param eventType the event type
148151
* @return a Collection of ApplicationListeners
149152
* @see org.springframework.context.ApplicationListener
150153
*/
151-
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event) {
154+
protected Collection<ApplicationListener<?>> getApplicationListeners(
155+
ApplicationEvent event, ResolvableType eventType) {
156+
152157
Object source = event.getSource();
153158
Class<?> sourceType = (source != null ? source.getClass() : null);
154-
ListenerCacheKey cacheKey = new ListenerCacheKey(event.getClass(), sourceType);
159+
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
155160

156161
// Quick check for existing entry on ConcurrentHashMap...
157162
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
@@ -169,26 +174,28 @@ protected Collection<ApplicationListener<?>> getApplicationListeners(Application
169174
return retriever.getApplicationListeners();
170175
}
171176
retriever = new ListenerRetriever(true);
172-
Collection<ApplicationListener<?>> listeners = retrieveApplicationListeners(event, sourceType, retriever);
177+
Collection<ApplicationListener<?>> listeners =
178+
retrieveApplicationListeners(event, eventType, sourceType, retriever);
173179
this.retrieverCache.put(cacheKey, retriever);
174180
return listeners;
175181
}
176182
}
177183
else {
178184
// No ListenerRetriever caching -> no synchronization necessary
179-
return retrieveApplicationListeners(event, sourceType, null);
185+
return retrieveApplicationListeners(event, eventType, sourceType, null);
180186
}
181187
}
182188

183189
/**
184190
* Actually retrieve the application listeners for the given event and source type.
185191
* @param event the application event
192+
* @param eventType the event type
186193
* @param sourceType the event source type
187194
* @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
188195
* @return the pre-filtered list of application listeners for the given event and source type
189196
*/
190197
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
191-
ApplicationEvent event, Class<?> sourceType, ListenerRetriever retriever) {
198+
ApplicationEvent event, ResolvableType eventType, Class<?> sourceType, ListenerRetriever retriever) {
192199

193200
LinkedList<ApplicationListener<?>> allListeners = new LinkedList<ApplicationListener<?>>();
194201
Set<ApplicationListener<?>> listeners;
@@ -198,7 +205,7 @@ private Collection<ApplicationListener<?>> retrieveApplicationListeners(
198205
listenerBeans = new LinkedHashSet<String>(this.defaultRetriever.applicationListenerBeans);
199206
}
200207
for (ApplicationListener<?> listener : listeners) {
201-
if (supportsEvent(listener, event.getClass(), sourceType)) {
208+
if (supportsEvent(listener, eventType, sourceType)) {
202209
if (retriever != null) {
203210
retriever.applicationListeners.add(listener);
204211
}
@@ -210,10 +217,10 @@ private Collection<ApplicationListener<?>> retrieveApplicationListeners(
210217
for (String listenerBeanName : listenerBeans) {
211218
try {
212219
Class<?> listenerType = beanFactory.getType(listenerBeanName);
213-
if (listenerType == null || supportsEvent(listenerType, event)) {
220+
if (listenerType == null || supportsEvent(listenerType, eventType)) {
214221
ApplicationListener<?> listener =
215222
beanFactory.getBean(listenerBeanName, ApplicationListener.class);
216-
if (!allListeners.contains(listener) && supportsEvent(listener, event.getClass(), sourceType)) {
223+
if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
217224
if (retriever != null) {
218225
retriever.applicationListenerBeans.add(listenerBeanName);
219226
}
@@ -236,37 +243,38 @@ private Collection<ApplicationListener<?>> retrieveApplicationListeners(
236243
* type before trying to instantiate it.
237244
* <p>If this method returns {@code true} for a given listener as a first pass,
238245
* the listener instance will get retrieved and fully evaluated through a
239-
* {@link #supportsEvent(ApplicationListener, Class, Class)} call afterwards.
246+
* {@link #supportsEvent(ApplicationListener,ResolvableType, Class)} call afterwards.
240247
* @param listenerType the listener's type as determined by the BeanFactory
241-
* @param event the event to check
248+
* @param eventType the event type to check
242249
* @return whether the given listener should be included in the candidates
243250
* for the given event type
244251
*/
245-
protected boolean supportsEvent(Class<?> listenerType, ApplicationEvent event) {
246-
if (SmartApplicationListener.class.isAssignableFrom(listenerType)) {
252+
protected boolean supportsEvent(Class<?> listenerType, ResolvableType eventType) {
253+
if (GenericApplicationListener.class.isAssignableFrom(listenerType)
254+
|| SmartApplicationListener.class.isAssignableFrom(listenerType)) {
247255
return true;
248256
}
249-
Class<?> declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType);
250-
return (declaredEventType == null || declaredEventType.isInstance(event));
257+
ResolvableType declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType);
258+
return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType));
251259
}
252260

253261
/**
254262
* Determine whether the given listener supports the given event.
255263
* <p>The default implementation detects the {@link SmartApplicationListener}
256-
* interface. In case of a standard {@link ApplicationListener}, a
257-
* {@link GenericApplicationListenerAdapter} will be used to introspect
258-
* the generically declared type of the target listener.
264+
* and {@link GenericApplicationListener} interfaces. In case of a standard
265+
* {@link ApplicationListener}, a {@link GenericApplicationListenerAdapter}
266+
* will be used to introspect the generically declared type of the target listener.
259267
* @param listener the target listener to check
260268
* @param eventType the event type to check against
261269
* @param sourceType the source type to check against
262270
* @return whether the given listener should be included in the candidates
263271
* for the given event type
264272
*/
265273
protected boolean supportsEvent(ApplicationListener<?> listener,
266-
Class<? extends ApplicationEvent> eventType, Class<?> sourceType) {
274+
ResolvableType eventType, Class<?> sourceType) {
267275

268-
SmartApplicationListener smartListener = (listener instanceof SmartApplicationListener ?
269-
(SmartApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
276+
GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
277+
(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
270278
return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
271279
}
272280

@@ -276,11 +284,11 @@ protected boolean supportsEvent(ApplicationListener<?> listener,
276284
*/
277285
private static class ListenerCacheKey {
278286

279-
private final Class<?> eventType;
287+
private final ResolvableType eventType;
280288

281289
private final Class<?> sourceType;
282290

283-
public ListenerCacheKey(Class<?> eventType, Class<?> sourceType) {
291+
public ListenerCacheKey(ResolvableType eventType, Class<?> sourceType) {
284292
this.eventType = eventType;
285293
this.sourceType = sourceType;
286294
}

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import org.springframework.context.ApplicationEvent;
2020
import org.springframework.context.ApplicationListener;
21+
import org.springframework.core.ResolvableType;
2122

2223
/**
2324
* Interface to be implemented by objects that can manage a number of
@@ -29,6 +30,7 @@
2930
*
3031
* @author Rod Johnson
3132
* @author Juergen Hoeller
33+
* @author Stephane Nicoll
3234
*/
3335
public interface ApplicationEventMulticaster {
3436

@@ -65,8 +67,19 @@ public interface ApplicationEventMulticaster {
6567

6668
/**
6769
* Multicast the given application event to appropriate listeners.
70+
* <p>Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)}
71+
* if possible as it provides a better support for generics-based events.
6872
* @param event the event to multicast
6973
*/
7074
void multicastEvent(ApplicationEvent event);
7175

76+
/**
77+
* Multicast the given application event to appropriate listeners.
78+
* <p>If the {@code eventType} is {@code null}, a default type is built
79+
* based on the {@code event} instance.
80+
* @param event the event to multicast
81+
* @param eventType the type of event (can be null)
82+
*/
83+
void multicastEvent(ApplicationEvent event, ResolvableType eventType);
84+
7285
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2002-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.event;
18+
19+
import org.springframework.context.ApplicationEvent;
20+
import org.springframework.context.ApplicationListener;
21+
import org.springframework.core.Ordered;
22+
import org.springframework.core.ResolvableType;
23+
24+
/**
25+
* Extended variant of the standard {@link ApplicationListener} interface,
26+
* exposing further metadata such as the supported event type.
27+
*
28+
* <p>As of Spring Framework 4.2, supersedes {@link SmartApplicationListener} with
29+
* proper handling of generics-based event.
30+
*
31+
* @author Stephane Nicoll
32+
* @since 4.2
33+
*/
34+
public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
35+
36+
/**
37+
* Determine whether this listener actually supports the given event type.
38+
*/
39+
boolean supportsEventType(ResolvableType eventType);
40+
41+
/**
42+
* Determine whether this listener actually supports the given source type.
43+
*/
44+
boolean supportsSourceType(Class<?> sourceType);
45+
46+
}

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

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,22 +19,24 @@
1919
import org.springframework.aop.support.AopUtils;
2020
import org.springframework.context.ApplicationEvent;
2121
import org.springframework.context.ApplicationListener;
22-
import org.springframework.core.GenericTypeResolver;
2322
import org.springframework.core.Ordered;
23+
import org.springframework.core.ResolvableType;
2424
import org.springframework.util.Assert;
2525

2626
/**
27-
* {@link SmartApplicationListener} adapter that determines supported event types
27+
* {@link GenericApplicationListener} adapter that determines supported event types
2828
* through introspecting the generically declared type of the target listener.
2929
*
3030
* @author Juergen Hoeller
31+
* @author Stephane Nicoll
3132
* @since 3.0
3233
* @see org.springframework.context.ApplicationListener#onApplicationEvent
3334
*/
34-
public class GenericApplicationListenerAdapter implements SmartApplicationListener {
35+
public class GenericApplicationListenerAdapter implements GenericApplicationListener {
3536

3637
private final ApplicationListener<ApplicationEvent> delegate;
3738

39+
private final ResolvableType declaredEventType;
3840

3941
/**
4042
* Create a new GenericApplicationListener for the given delegate.
@@ -44,6 +46,7 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen
4446
public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
4547
Assert.notNull(delegate, "Delegate listener must not be null");
4648
this.delegate = (ApplicationListener<ApplicationEvent>) delegate;
49+
this.declaredEventType = resolveDeclaredEventType(this.delegate);
4750
}
4851

4952

@@ -53,30 +56,51 @@ public void onApplicationEvent(ApplicationEvent event) {
5356
}
5457

5558
@Override
56-
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
57-
Class<?> declaredEventType = resolveDeclaredEventType(this.delegate.getClass());
58-
if (declaredEventType == null || declaredEventType.equals(ApplicationEvent.class)) {
59-
Class<?> targetClass = AopUtils.getTargetClass(this.delegate);
60-
if (targetClass != this.delegate.getClass()) {
61-
declaredEventType = resolveDeclaredEventType(targetClass);
62-
}
59+
@SuppressWarnings("unchecked")
60+
public boolean supportsEventType(ResolvableType eventType) {
61+
if (this.delegate instanceof SmartApplicationListener) {
62+
Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.getRawClass();
63+
return ((SmartApplicationListener) this.delegate).supportsEventType(eventClass);
64+
}
65+
else {
66+
return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
6367
}
64-
return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType));
6568
}
6669

6770
@Override
6871
public boolean supportsSourceType(Class<?> sourceType) {
69-
return true;
72+
if (this.delegate instanceof SmartApplicationListener) {
73+
return ((SmartApplicationListener) this.delegate).supportsSourceType(sourceType);
74+
}
75+
else {
76+
return true;
77+
}
7078
}
7179

7280
@Override
7381
public int getOrder() {
7482
return (this.delegate instanceof Ordered ? ((Ordered) this.delegate).getOrder() : Ordered.LOWEST_PRECEDENCE);
7583
}
7684

85+
static ResolvableType resolveDeclaredEventType(Class<?> listenerType) {
86+
ResolvableType resolvableType = ResolvableType.forClass(listenerType).as(ApplicationListener.class);
87+
if (resolvableType == null || !resolvableType.hasGenerics()) {
88+
return null;
89+
}
90+
return resolvableType.getGeneric();
91+
}
92+
93+
private static ResolvableType resolveDeclaredEventType(ApplicationListener<ApplicationEvent> listener) {
94+
ResolvableType declaredEventType = resolveDeclaredEventType(listener.getClass());
95+
if (declaredEventType == null || declaredEventType.isAssignableFrom(
96+
ResolvableType.forClass(ApplicationEvent.class))) {
7797

78-
static Class<?> resolveDeclaredEventType(Class<?> listenerType) {
79-
return GenericTypeResolver.resolveTypeArgument(listenerType, ApplicationListener.class);
98+
Class<?> targetClass = AopUtils.getTargetClass(listener);
99+
if (targetClass != listener.getClass()) {
100+
declaredEventType = resolveDeclaredEventType(targetClass);
101+
}
102+
}
103+
return declaredEventType;
80104
}
81105

82106
}

0 commit comments

Comments
 (0)