From 0e1b04d0829caa3a64ccab655c971312491f46ba Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 12 Nov 2009 17:47:34 +0000 Subject: [PATCH] inner beans detected as ApplicationListeners as well (SPR-6049) --- .../ConfigurableApplicationContext.java | 4 +- .../support/AbstractApplicationContext.java | 16 ++++-- .../ApplicationContextAwareProcessor.java | 50 ++++++++++++++++--- .../event/ApplicationContextEventTests.java | 14 ++++++ 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java index 27407621e3b..ad56ae9a256 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java @@ -99,8 +99,8 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life * Add a new ApplicationListener that will be notified on context events * such as context refresh and context shutdown. *

Note that any ApplicationListener registered here will be applied - * on refresh of this context. If a listener is added after the initial - * refresh, it will be applied on next refresh of the context. + * on refresh if the context is not active yet, or on the fly with the + * current event multicaster in case of a context that is already active. * @param listener the ApplicationListener to register * @see org.springframework.context.event.ContextRefreshedEvent * @see org.springframework.context.event.ContextClosedEvent diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 4a91e556e30..82a412d7760 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -20,11 +20,14 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.security.AccessControlException; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -193,7 +196,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader private ApplicationEventMulticaster applicationEventMulticaster; /** Statically specified listeners */ - private List applicationListeners = new ArrayList(); + private Set applicationListeners = new LinkedHashSet(); /** @@ -362,13 +365,18 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader } public void addApplicationListener(ApplicationListener listener) { - this.applicationListeners.add(listener); + if (isActive()) { + addListener(listener); + } + else { + this.applicationListeners.add(listener); + } } /** * Return the list of statically specified ApplicationListeners. */ - public List getApplicationListeners() { + public Collection getApplicationListeners() { return this.applicationListeners; } @@ -817,7 +825,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let post-processors apply to them! String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); - for (final String lisName : listenerBeanNames) { + for (String lisName : listenerBeanNames) { getApplicationEventMulticaster().addApplicationListenerBean(lisName); } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java index 129d679db78..45f12067b3e 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java @@ -19,11 +19,18 @@ package org.springframework.context.support; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.MessageSourceAware; import org.springframework.context.ResourceLoaderAware; @@ -48,10 +55,14 @@ import org.springframework.context.ResourceLoaderAware; * @see org.springframework.context.ApplicationContextAware * @see org.springframework.context.support.AbstractApplicationContext#refresh() */ -class ApplicationContextAwareProcessor implements BeanPostProcessor { +class ApplicationContextAwareProcessor implements MergedBeanDefinitionPostProcessor { + + private final Log logger = LogFactory.getLog(getClass()); private final ConfigurableApplicationContext applicationContext; + private final Map singletonNames = new ConcurrentHashMap(); + /** * Create a new ApplicationContextAwareProcessor for the given context. @@ -61,7 +72,13 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { } - public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException { + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { + if (!this.applicationContext.containsBean(beanName) && beanDefinition.isSingleton()) { + this.singletonNames.put(beanName, Boolean.TRUE); + } + } + + public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException { AccessControlContext acc = null; if (System.getSecurityManager() != null && @@ -73,19 +90,19 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { if (acc != null) { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { - doProcess(bean); + doProcess(bean, beanName); return null; } }, acc); } else { - doProcess(bean); + doProcess(bean, beanName); } return bean; } - private void doProcess(Object bean) { + private void doProcess(Object bean, String beanName) { if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } @@ -98,6 +115,27 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } + + if (bean instanceof ApplicationListener) { + if (!this.applicationContext.containsBean(beanName)) { + // not a top-level bean - not detected as a listener by getBeanNamesForType retrieval + Boolean flag = this.singletonNames.get(beanName); + if (Boolean.TRUE.equals(flag)) { + // inner singleton bean: register on the fly + this.applicationContext.addApplicationListener((ApplicationListener) bean); + } + else if (flag == null) { + // inner bean with other scope - can't reliably process events + if (logger.isWarnEnabled()) { + logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " + + "but is not reachable for event multicasting by its containing ApplicationContext " + + "because it does not have singleton scope. Only top-level listener beans are allowed " + + "to be of non-singleton scope."); + } + this.singletonNames.put(beanName, Boolean.FALSE); + } + } + } } public Object postProcessAfterInitialization(Object bean, String name) { diff --git a/org.springframework.context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/org.springframework.context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index d70e10c9b7f..f7803fd4eef 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -25,6 +25,7 @@ import static org.easymock.EasyMock.*; import static org.junit.Assert.*; import org.junit.Test; +import org.springframework.beans.TestBean; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; @@ -126,6 +127,19 @@ public class ApplicationContextEventTests { assertEquals("The event was not received by the listener", 2, broadcaster.receivedCount); } + @Test + public void innerBeanAsListener() { + StaticApplicationContext context = new StaticApplicationContext(); + RootBeanDefinition listenerDef = new RootBeanDefinition(TestBean.class); + listenerDef.getPropertyValues().addPropertyValue("friends", new RootBeanDefinition(BeanThatListens.class)); + context.registerBeanDefinition("listener", listenerDef); + context.refresh(); + context.publishEvent(new MyEvent(this)); + context.publishEvent(new MyEvent(this)); + TestBean listener = context.getBean(TestBean.class); + assertEquals(3, ((BeanThatListens) listener.getFriends().iterator().next()).getEventCount()); + } + public static class MyEvent extends ApplicationEvent {