Skip to content

Commit 9abf249

Browse files
committed
Explicitly replace target ApplicationListener with singleton proxy, if any (avoiding double registration/invocation)
Issue: SPR-15452
1 parent 3efb76c commit 9abf249

File tree

3 files changed

+54
-11
lines changed

3 files changed

+54
-11
lines changed

spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 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.
@@ -43,6 +43,25 @@
4343
*/
4444
public abstract class AopProxyUtils {
4545

46+
/**
47+
* Obtain the singleton target object behind the given proxy, if any.
48+
* @param candidate the (potential) proxy to check
49+
* @return the singleton target object managed in a {@link SingletonTargetSource},
50+
* or {@code null} in any other case (not a proxy, not an existing singleton target)
51+
* @since 4.3.8
52+
* @see Advised#getTargetSource()
53+
* @see SingletonTargetSource#getTarget()
54+
*/
55+
public static Object getSingletonTarget(Object candidate) {
56+
if (candidate instanceof Advised) {
57+
TargetSource targetSource = ((Advised) candidate).getTargetSource();
58+
if (targetSource instanceof SingletonTargetSource) {
59+
return ((SingletonTargetSource) targetSource).getTarget();
60+
}
61+
}
62+
return null;
63+
}
64+
4665
/**
4766
* Determine the ultimate target class of the given bean instance, traversing
4867
* not only a top-level proxy but any number of nested proxies as well —
@@ -59,14 +78,7 @@ public static Class<?> ultimateTargetClass(Object candidate) {
5978
Class<?> result = null;
6079
while (current instanceof TargetClassAware) {
6180
result = ((TargetClassAware) current).getTargetClass();
62-
Object nested = null;
63-
if (current instanceof Advised) {
64-
TargetSource targetSource = ((Advised) current).getTargetSource();
65-
if (targetSource instanceof SingletonTargetSource) {
66-
nested = ((SingletonTargetSource) targetSource).getTarget();
67-
}
68-
}
69-
current = nested;
81+
current = getSingletonTarget(current);
7082
}
7183
if (result == null) {
7284
result = (AopUtils.isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Set;
2424
import java.util.concurrent.ConcurrentHashMap;
2525

26+
import org.springframework.aop.framework.AopProxyUtils;
2627
import org.springframework.beans.factory.BeanClassLoaderAware;
2728
import org.springframework.beans.factory.BeanFactory;
2829
import org.springframework.beans.factory.BeanFactoryAware;
@@ -98,6 +99,12 @@ private BeanFactory getBeanFactory() {
9899
@Override
99100
public void addApplicationListener(ApplicationListener<?> listener) {
100101
synchronized (this.retrievalMutex) {
102+
// Explicitly remove target for a proxy, if registered already,
103+
// in order to avoid double invocations of the same listener.
104+
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
105+
if (singletonTarget instanceof ApplicationListener) {
106+
this.defaultRetriever.applicationListeners.remove(singletonTarget);
107+
}
101108
this.defaultRetriever.applicationListeners.add(listener);
102109
this.retrieverCache.clear();
103110
}

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 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.
@@ -17,6 +17,8 @@
1717
package org.springframework.context.event;
1818

1919
import java.util.HashSet;
20+
import java.util.LinkedList;
21+
import java.util.List;
2022
import java.util.Set;
2123
import java.util.concurrent.Executor;
2224

@@ -184,6 +186,7 @@ public void orderedListeners() {
184186

185187
smc.multicastEvent(new MyEvent(this));
186188
smc.multicastEvent(new MyOtherEvent(this));
189+
assertEquals(2, listener1.seenEvents.size());
187190
}
188191

189192
@Test
@@ -197,6 +200,7 @@ public void orderedListenersWithAnnotation() {
197200

198201
smc.multicastEvent(new MyEvent(this));
199202
smc.multicastEvent(new MyOtherEvent(this));
203+
assertEquals(2, listener1.seenEvents.size());
200204
}
201205

202206
@Test
@@ -213,6 +217,26 @@ public void proxiedListeners() {
213217

214218
smc.multicastEvent(new MyEvent(this));
215219
smc.multicastEvent(new MyOtherEvent(this));
220+
assertEquals(2, listener1.seenEvents.size());
221+
}
222+
223+
@Test
224+
@SuppressWarnings("unchecked")
225+
public void proxiedListenersMixedWithTargetListeners() {
226+
MyOrderedListener1 listener1 = new MyOrderedListener1();
227+
MyOrderedListener2 listener2 = new MyOrderedListener2(listener1);
228+
ApplicationListener<ApplicationEvent> proxy1 = (ApplicationListener<ApplicationEvent>) new ProxyFactory(listener1).getProxy();
229+
ApplicationListener<ApplicationEvent> proxy2 = (ApplicationListener<ApplicationEvent>) new ProxyFactory(listener2).getProxy();
230+
231+
SimpleApplicationEventMulticaster smc = new SimpleApplicationEventMulticaster();
232+
smc.addApplicationListener(listener1);
233+
smc.addApplicationListener(listener2);
234+
smc.addApplicationListener(proxy1);
235+
smc.addApplicationListener(proxy2);
236+
237+
smc.multicastEvent(new MyEvent(this));
238+
smc.multicastEvent(new MyOtherEvent(this));
239+
assertEquals(2, listener1.seenEvents.size());
216240
}
217241

218242
@Test
@@ -459,7 +483,7 @@ public MyOtherEvent(Object source) {
459483

460484
public static class MyOrderedListener1 implements ApplicationListener<ApplicationEvent>, Ordered {
461485

462-
public final Set<ApplicationEvent> seenEvents = new HashSet<>();
486+
public final List<ApplicationEvent> seenEvents = new LinkedList<>();
463487

464488
@Override
465489
public void onApplicationEvent(ApplicationEvent event) {

0 commit comments

Comments
 (0)