Browse Source

ApplicationListener detection for inner beans behind post-processors

Issue: SPR-14783
pull/1226/head
Juergen Hoeller 8 years ago
parent
commit
c946924431
  1. 7
      spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
  2. 119
      spring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.java
  3. 81
      spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java
  4. 70
      spring-context/src/test/java/org/springframework/context/support/BeanFactoryPostProcessorTests.java

7
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.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
import org.springframework.context.HierarchicalMessageSource; import org.springframework.context.HierarchicalMessageSource;
import org.springframework.context.LifecycleProcessor; import org.springframework.context.LifecycleProcessor;
@ -630,11 +631,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Configure the bean factory with context callbacks. // Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class); beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class); beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class); beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class); beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
// BeanFactory interface not registered as resolvable type in a plain factory. // BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean. // 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(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.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. // Detect a LoadTimeWeaver and prepare for weaving, if found.
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));

119
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.
*
* <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();
}
}

81
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.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 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.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition; 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.OrderComparator;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered; import org.springframework.core.PriorityOrdered;
@ -249,6 +244,8 @@ class PostProcessorRegistrationDelegate {
sortPostProcessors(beanFactory, internalPostProcessors); sortPostProcessors(beanFactory, internalPostProcessors);
registerBeanPostProcessors(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)); 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);
}
}
} }

70
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext; 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.core.PriorityOrdered;
import org.springframework.tests.sample.beans.TestBean; import org.springframework.tests.sample.beans.TestBean;
import org.springframework.util.Assert;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -119,6 +123,24 @@ public class BeanFactoryPostProcessorTests {
assertTrue(ac.getBean(TestBeanFactoryPostProcessor.class).wasCalled); 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 { 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…
Cancel
Save