From 9abf249cee58f1a23ff6f2f99ab56082a65aa617 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 15 Apr 2017 14:10:28 +0200 Subject: [PATCH] Explicitly replace target ApplicationListener with singleton proxy, if any (avoiding double registration/invocation) Issue: SPR-15452 --- .../aop/framework/AopProxyUtils.java | 30 +++++++++++++------ .../AbstractApplicationEventMulticaster.java | 7 +++++ .../event/ApplicationContextEventTests.java | 28 +++++++++++++++-- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java index 15535a5e270..7c611bad313 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java @@ -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"); * 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 { + /** + * 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 * 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; while (current instanceof TargetClassAware) { result = ((TargetClassAware) current).getTargetClass(); - Object nested = null; - if (current instanceof Advised) { - TargetSource targetSource = ((Advised) current).getTargetSource(); - if (targetSource instanceof SingletonTargetSource) { - nested = ((SingletonTargetSource) targetSource).getTarget(); - } - } - current = nested; + current = getSingletonTarget(current); } if (result == null) { result = (AopUtils.isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass()); 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 f594fee5a6c..81577cb77fb 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 @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -98,6 +99,12 @@ public abstract class AbstractApplicationEventMulticaster @Override public void addApplicationListener(ApplicationListener listener) { 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.retrieverCache.clear(); } 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 73e9ebd505c..7bf60e7ac96 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-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -17,6 +17,8 @@ package org.springframework.context.event; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import java.util.concurrent.Executor; @@ -184,6 +186,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen smc.multicastEvent(new MyEvent(this)); smc.multicastEvent(new MyOtherEvent(this)); + assertEquals(2, listener1.seenEvents.size()); } @Test @@ -197,6 +200,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen smc.multicastEvent(new MyEvent(this)); smc.multicastEvent(new MyOtherEvent(this)); + assertEquals(2, listener1.seenEvents.size()); } @Test @@ -213,6 +217,26 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen smc.multicastEvent(new MyEvent(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 proxy1 = (ApplicationListener) new ProxyFactory(listener1).getProxy(); + ApplicationListener proxy2 = (ApplicationListener) 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 @@ -459,7 +483,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen public static class MyOrderedListener1 implements ApplicationListener, Ordered { - public final Set seenEvents = new HashSet<>(); + public final List seenEvents = new LinkedList<>(); @Override public void onApplicationEvent(ApplicationEvent event) {