Explicitly replace target ApplicationListener with singleton proxy, if any (avoiding double registration/invocation)
Issue: SPR-15452
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -43,6 +43,25 @@ import org.springframework.util.ObjectUtils;
|
|||||||
*/
|
*/
|
||||||
public abstract class AopProxyUtils {
|
public abstract class AopProxyUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the singleton target object behind the given proxy, if any.
|
||||||
|
* @param candidate the (potential) proxy to check
|
||||||
|
* @return the singleton target object managed in a {@link SingletonTargetSource},
|
||||||
|
* or {@code null} in any other case (not a proxy, not an existing singleton target)
|
||||||
|
* @since 4.3.8
|
||||||
|
* @see Advised#getTargetSource()
|
||||||
|
* @see SingletonTargetSource#getTarget()
|
||||||
|
*/
|
||||||
|
public static Object getSingletonTarget(Object candidate) {
|
||||||
|
if (candidate instanceof Advised) {
|
||||||
|
TargetSource targetSource = ((Advised) candidate).getTargetSource();
|
||||||
|
if (targetSource instanceof SingletonTargetSource) {
|
||||||
|
return ((SingletonTargetSource) targetSource).getTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the ultimate target class of the given bean instance, traversing
|
* Determine the ultimate target class of the given bean instance, traversing
|
||||||
* not only a top-level proxy but any number of nested proxies as well —
|
* not only a top-level proxy but any number of nested proxies as well —
|
||||||
@@ -59,14 +78,7 @@ public abstract class AopProxyUtils {
|
|||||||
Class<?> result = null;
|
Class<?> result = null;
|
||||||
while (current instanceof TargetClassAware) {
|
while (current instanceof TargetClassAware) {
|
||||||
result = ((TargetClassAware) current).getTargetClass();
|
result = ((TargetClassAware) current).getTargetClass();
|
||||||
Object nested = null;
|
current = getSingletonTarget(current);
|
||||||
if (current instanceof Advised) {
|
|
||||||
TargetSource targetSource = ((Advised) current).getTargetSource();
|
|
||||||
if (targetSource instanceof SingletonTargetSource) {
|
|
||||||
nested = ((SingletonTargetSource) targetSource).getTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
current = nested;
|
|
||||||
}
|
}
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = (AopUtils.isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
|
result = (AopUtils.isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.springframework.aop.framework.AopProxyUtils;
|
||||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
@@ -98,6 +99,12 @@ public abstract class AbstractApplicationEventMulticaster
|
|||||||
@Override
|
@Override
|
||||||
public void addApplicationListener(ApplicationListener<?> listener) {
|
public void addApplicationListener(ApplicationListener<?> listener) {
|
||||||
synchronized (this.retrievalMutex) {
|
synchronized (this.retrievalMutex) {
|
||||||
|
// Explicitly remove target for a proxy, if registered already,
|
||||||
|
// in order to avoid double invocations of the same listener.
|
||||||
|
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
|
||||||
|
if (singletonTarget instanceof ApplicationListener) {
|
||||||
|
this.defaultRetriever.applicationListeners.remove(singletonTarget);
|
||||||
|
}
|
||||||
this.defaultRetriever.applicationListeners.add(listener);
|
this.defaultRetriever.applicationListeners.add(listener);
|
||||||
this.retrieverCache.clear();
|
this.retrieverCache.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,8 @@
|
|||||||
package org.springframework.context.event;
|
package org.springframework.context.event;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@@ -184,6 +186,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
|||||||
|
|
||||||
smc.multicastEvent(new MyEvent(this));
|
smc.multicastEvent(new MyEvent(this));
|
||||||
smc.multicastEvent(new MyOtherEvent(this));
|
smc.multicastEvent(new MyOtherEvent(this));
|
||||||
|
assertEquals(2, listener1.seenEvents.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -197,6 +200,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
|||||||
|
|
||||||
smc.multicastEvent(new MyEvent(this));
|
smc.multicastEvent(new MyEvent(this));
|
||||||
smc.multicastEvent(new MyOtherEvent(this));
|
smc.multicastEvent(new MyOtherEvent(this));
|
||||||
|
assertEquals(2, listener1.seenEvents.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -213,6 +217,26 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
|||||||
|
|
||||||
smc.multicastEvent(new MyEvent(this));
|
smc.multicastEvent(new MyEvent(this));
|
||||||
smc.multicastEvent(new MyOtherEvent(this));
|
smc.multicastEvent(new MyOtherEvent(this));
|
||||||
|
assertEquals(2, listener1.seenEvents.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void proxiedListenersMixedWithTargetListeners() {
|
||||||
|
MyOrderedListener1 listener1 = new MyOrderedListener1();
|
||||||
|
MyOrderedListener2 listener2 = new MyOrderedListener2(listener1);
|
||||||
|
ApplicationListener<ApplicationEvent> proxy1 = (ApplicationListener<ApplicationEvent>) new ProxyFactory(listener1).getProxy();
|
||||||
|
ApplicationListener<ApplicationEvent> proxy2 = (ApplicationListener<ApplicationEvent>) new ProxyFactory(listener2).getProxy();
|
||||||
|
|
||||||
|
SimpleApplicationEventMulticaster smc = new SimpleApplicationEventMulticaster();
|
||||||
|
smc.addApplicationListener(listener1);
|
||||||
|
smc.addApplicationListener(listener2);
|
||||||
|
smc.addApplicationListener(proxy1);
|
||||||
|
smc.addApplicationListener(proxy2);
|
||||||
|
|
||||||
|
smc.multicastEvent(new MyEvent(this));
|
||||||
|
smc.multicastEvent(new MyOtherEvent(this));
|
||||||
|
assertEquals(2, listener1.seenEvents.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -459,7 +483,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
|||||||
|
|
||||||
public static class MyOrderedListener1 implements ApplicationListener<ApplicationEvent>, Ordered {
|
public static class MyOrderedListener1 implements ApplicationListener<ApplicationEvent>, Ordered {
|
||||||
|
|
||||||
public final Set<ApplicationEvent> seenEvents = new HashSet<>();
|
public final List<ApplicationEvent> seenEvents = new LinkedList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(ApplicationEvent event) {
|
public void onApplicationEvent(ApplicationEvent event) {
|
||||||
|
|||||||
Reference in New Issue
Block a user