ApplicationListener detection for inner beans behind post-processors
Issue: SPR-14783
This commit is contained in:
parent
5ac5ec1046
commit
c946924431
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
* <p>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<String, Boolean> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
* <p>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<String, Boolean> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue