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 f22e0ceb721..76be0c05252 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 @@ -263,6 +263,24 @@ public abstract class AbstractApplicationEventMulticaster if (supportsEvent(beanFactory, listenerBeanName, eventType)) { ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); + + // Despite best efforts to avoid it, unwrapped proxies (singleton targets) can end up in the + // list of programmatically registered listeners. In order to avoid duplicates, we need to find + // and replace them by their proxy counterparts, because if both a proxy and its target end up + // in 'allListeners', listeners will fire twice. + ApplicationListener unwrappedListener = + (ApplicationListener) AopProxyUtils.getSingletonTarget(listener); + if (listener != unwrappedListener) { + if (filteredListeners != null && filteredListeners.contains(unwrappedListener)) { + filteredListeners.remove(unwrappedListener); + filteredListeners.add(listener); + } + if (allListeners.contains(unwrappedListener)) { + allListeners.remove(unwrappedListener); + allListeners.add(listener); + } + } + if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { if (beanFactory.isSingleton(listenerBeanName)) {