diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index c42f43ae48..22fd285427 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -47,6 +47,7 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.context.EnvironmentAware; import org.springframework.context.HierarchicalMessageSource; import org.springframework.context.LifecycleProcessor; @@ -630,11 +631,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Configure the bean factory with context callbacks. beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); + beanFactory.ignoreDependencyInterface(EnvironmentAware.class); + beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class); beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class); beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class); beanFactory.ignoreDependencyInterface(MessageSourceAware.class); beanFactory.ignoreDependencyInterface(ApplicationContextAware.class); - beanFactory.ignoreDependencyInterface(EnvironmentAware.class); // BeanFactory interface not registered as resolvable type in a plain factory. // MessageSource registered (and found for autowiring) as a bean. @@ -643,6 +645,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this); beanFactory.registerResolvableDependency(ApplicationContext.class, this); + // Register early post-processor for detecting inner beans as ApplicationListeners. + beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this)); + // Detect a LoadTimeWeaver and prepare for weaving, if found. if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); diff --git a/spring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.java b/spring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.java new file mode 100644 index 0000000000..b18bc40ba1 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.java @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.support; + +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.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationEventMulticaster; + +/** + * {@code BeanPostProcessor} that detects beans which implement the {@code ApplicationListener} + * interface. This catches beans that can't reliably be detected by {@code getBeanNamesForType} + * and related operations which only work against top-level beans. + * + *

With standard Java serialization, this post-processor won't get serialized as part of + * {@code DisposableBeanAdapter} to begin with. However, with alternative serialization + * mechanisms, {@code DisposableBeanAdapter.writeReplace} might not get used at all, so we + * defensively mark this post-processor's field state as {@code transient}. + * + * @author Juergen Hoeller + * @since 4.3.4 + */ +class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor { + + private static final Log logger = LogFactory.getLog(ApplicationListenerDetector.class); + + private transient final AbstractApplicationContext applicationContext; + + private transient final Map singletonNames = new ConcurrentHashMap<>(256); + + + public ApplicationListenerDetector(AbstractApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + + @Override + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { + if (this.applicationContext != null && beanDefinition.isSingleton()) { + this.singletonNames.put(beanName, Boolean.TRUE); + } + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (this.applicationContext != null && bean instanceof ApplicationListener) { + // potentially not detected as a listener by getBeanNamesForType retrieval + Boolean flag = this.singletonNames.get(beanName); + if (Boolean.TRUE.equals(flag)) { + // singleton bean (top-level or inner): register on the fly + this.applicationContext.addApplicationListener((ApplicationListener) bean); + } + else if (flag == null) { + if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) { + // inner bean with other scope - can't reliably process events + 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); + } + } + return bean; + } + + @Override + public void postProcessBeforeDestruction(Object bean, String beanName) { + if (bean instanceof ApplicationListener) { + ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster(); + multicaster.removeApplicationListener((ApplicationListener) bean); + multicaster.removeApplicationListenerBean(beanName); + } + } + + @Override + public boolean requiresDestruction(Object bean) { + return (bean instanceof ApplicationListener); + } + + + @Override + public boolean equals(Object other) { + return (this == other || (other instanceof ApplicationListenerDetector && + this.applicationContext == ((ApplicationListenerDetector) other).applicationContext)); + } + + @Override + public int hashCode() { + return this.applicationContext.hashCode(); + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java index bcd55639f0..e3d6c6fc88 100644 --- a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java +++ b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java @@ -23,9 +23,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -34,14 +32,11 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ApplicationEventMulticaster; import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; @@ -249,6 +244,8 @@ class PostProcessorRegistrationDelegate { sortPostProcessors(beanFactory, internalPostProcessors); registerBeanPostProcessors(beanFactory, internalPostProcessors); + // Re-register post-processor for detecting inner beans as ApplicationListeners, + // moving it to the end of the processor chain (for picking up proxies etc). beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext)); } @@ -342,78 +339,4 @@ class PostProcessorRegistrationDelegate { } } - - /** - * {@code BeanPostProcessor} that detects beans which implement the {@code ApplicationListener} - * interface. This catches beans that can't reliably be detected by {@code getBeanNamesForType} - * and related operations which only work against top-level beans. - * - *

With standard Java serialization, this post-processor won't get serialized as part of - * {@code DisposableBeanAdapter} to begin with. However, with alternative serialization - * mechanisms, {@code DisposableBeanAdapter.writeReplace} might not get used at all, so we - * defensively mark this post-processor's field state as {@code transient}. - */ - private static class ApplicationListenerDetector - implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor { - - private static final Log logger = LogFactory.getLog(ApplicationListenerDetector.class); - - private transient final AbstractApplicationContext applicationContext; - - private transient final Map singletonNames = new ConcurrentHashMap<>(256); - - public ApplicationListenerDetector(AbstractApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - - @Override - public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { - if (this.applicationContext != null && beanDefinition.isSingleton()) { - this.singletonNames.put(beanName, Boolean.TRUE); - } - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) { - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) { - if (this.applicationContext != null && bean instanceof ApplicationListener) { - // potentially not detected as a listener by getBeanNamesForType retrieval - Boolean flag = this.singletonNames.get(beanName); - if (Boolean.TRUE.equals(flag)) { - // singleton bean (top-level or inner): register on the fly - this.applicationContext.addApplicationListener((ApplicationListener) bean); - } - else if (flag == null) { - if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) { - // inner bean with other scope - can't reliably process events - 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); - } - } - return bean; - } - - @Override - public void postProcessBeforeDestruction(Object bean, String beanName) { - if (bean instanceof ApplicationListener) { - ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster(); - multicaster.removeApplicationListener((ApplicationListener) bean); - multicaster.removeApplicationListenerBean(beanName); - } - } - - @Override - public boolean requiresDestruction(Object bean) { - return (bean instanceof ApplicationListener); - } - } - } diff --git a/spring-context/src/test/java/org/springframework/context/support/BeanFactoryPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/support/BeanFactoryPostProcessorTests.java index b68aef069d..1c9d437f03 100644 --- a/spring-context/src/test/java/org/springframework/context/support/BeanFactoryPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/BeanFactoryPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -28,8 +28,12 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProce import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.PriorityOrdered; import org.springframework.tests.sample.beans.TestBean; +import org.springframework.util.Assert; import static org.junit.Assert.*; @@ -119,6 +123,24 @@ public class BeanFactoryPostProcessorTests { assertTrue(ac.getBean(TestBeanFactoryPostProcessor.class).wasCalled); } + @Test + public void testBeanFactoryPostProcessorAsApplicationListener() { + StaticApplicationContext ac = new StaticApplicationContext(); + ac.registerBeanDefinition("bfpp", new RootBeanDefinition(ListeningBeanFactoryPostProcessor.class)); + ac.refresh(); + assertTrue(ac.getBean(ListeningBeanFactoryPostProcessor.class).received instanceof ContextRefreshedEvent); + } + + @Test + public void testBeanFactoryPostProcessorWithInnerBeanAsApplicationListener() { + StaticApplicationContext ac = new StaticApplicationContext(); + RootBeanDefinition rbd = new RootBeanDefinition(NestingBeanFactoryPostProcessor.class); + rbd.getPropertyValues().add("listeningBean", new RootBeanDefinition(ListeningBean.class)); + ac.registerBeanDefinition("bfpp", rbd); + ac.refresh(); + assertTrue(ac.getBean(NestingBeanFactoryPostProcessor.class).getListeningBean().received instanceof ContextRefreshedEvent); + } + public static class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @@ -170,4 +192,50 @@ public class BeanFactoryPostProcessorTests { } } + + public static class ListeningBeanFactoryPostProcessor implements BeanFactoryPostProcessor, ApplicationListener { + + public ApplicationEvent received; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + Assert.state(this.received == null, "Just one ContextRefreshedEvent expected"); + this.received = event; + } + } + + + public static class ListeningBean implements ApplicationListener { + + public ApplicationEvent received; + + @Override + public void onApplicationEvent(ApplicationEvent event) { + Assert.state(this.received == null, "Just one ContextRefreshedEvent expected"); + this.received = event; + } + } + + + public static class NestingBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + + private ListeningBean listeningBean; + + public void setListeningBean(ListeningBean listeningBean) { + this.listeningBean = listeningBean; + } + + public ListeningBean getListeningBean() { + return listeningBean; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + } + } + }