From bb971cecf15e8c7983cbaf7767851236d132889c Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 28 Aug 2013 12:49:53 +0200 Subject: [PATCH] A BeanDefinitionRegistryPostProcessor may register other BeanDefinitionRegistryPostProcessors We're using the same subtle PriorityOrdered/Ordered/non-ordered separation as for regular BeanFactoryPostProcessors and BeanPostProcessors now. Additionally, we're re-detecting BeanDefinitionRegistryPostProcessor bean names after every invocation phase, up until no further ones appear. Issue: SPR-10630 --- .../ConfigurationClassPostProcessor.java | 4 +- .../support/AbstractApplicationContext.java | 257 +----------- .../PostProcessorRegistrationDelegate.java | 370 ++++++++++++++++++ .../ConfigurationClassWithConditionTests.java | 102 +---- .../BeanFactoryPostProcessorTests.java | 70 +++- 5 files changed, 447 insertions(+), 356 deletions(-) create mode 100644 spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 834d40ee8e..7e67b3ace2 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -91,7 +91,7 @@ import static org.springframework.context.annotation.AnnotationConfigUtils.*; * @since 3.0 */ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, - ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware, Ordered { + ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware, PriorityOrdered { private static final String IMPORT_AWARE_PROCESSOR_BEAN_NAME = ConfigurationClassPostProcessor.class.getName() + ".importAwareProcessor"; @@ -377,7 +377,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo @Override public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE; + return Ordered.LOWEST_PRECEDENCE; // within PriorityOrdered } 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 fc2032f905..4387432d9a 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 @@ -21,14 +21,11 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.Date; -import java.util.HashSet; import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Locale; 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; @@ -39,12 +36,7 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 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.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.support.ResourceEditorRegistrar; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -70,9 +62,6 @@ import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.context.expression.StandardBeanExpressionResolver; import org.springframework.context.weaving.LoadTimeWeaverAware; import org.springframework.context.weaving.LoadTimeWeaverAwareProcessor; -import org.springframework.core.OrderComparator; -import org.springframework.core.Ordered; -import org.springframework.core.PriorityOrdered; import org.springframework.core.convert.ConversionService; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -617,96 +606,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader *

Must be called before singleton instantiation. */ protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { - // Invoke BeanDefinitionRegistryPostProcessors first, if any. - Set processedBeans = new HashSet(); - if (beanFactory instanceof BeanDefinitionRegistry) { - BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; - List regularPostProcessors = new LinkedList(); - List registryPostProcessors = - new LinkedList(); - for (BeanFactoryPostProcessor postProcessor : getBeanFactoryPostProcessors()) { - if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { - BeanDefinitionRegistryPostProcessor registryPostProcessor = - (BeanDefinitionRegistryPostProcessor) postProcessor; - registryPostProcessor.postProcessBeanDefinitionRegistry(registry); - registryPostProcessors.add(registryPostProcessor); - } - else { - regularPostProcessors.add(postProcessor); - } - } - Map beanMap = - beanFactory.getBeansOfType(BeanDefinitionRegistryPostProcessor.class, true, false); - List registryPostProcessorBeans = - new ArrayList(beanMap.values()); - OrderComparator.sort(registryPostProcessorBeans); - for (BeanDefinitionRegistryPostProcessor postProcessor : registryPostProcessorBeans) { - postProcessor.postProcessBeanDefinitionRegistry(registry); - } - invokeBeanFactoryPostProcessors(registryPostProcessors, beanFactory); - invokeBeanFactoryPostProcessors(registryPostProcessorBeans, beanFactory); - invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory); - processedBeans.addAll(beanMap.keySet()); - } - else { - // Invoke factory processors registered with the context instance. - invokeBeanFactoryPostProcessors(getBeanFactoryPostProcessors(), beanFactory); - } - - // Do not initialize FactoryBeans here: We need to leave all regular beans - // uninitialized to let the bean factory post-processors apply to them! - String[] postProcessorNames = - beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); - - // Separate between BeanFactoryPostProcessors that implement PriorityOrdered, - // Ordered, and the rest. - List priorityOrderedPostProcessors = new ArrayList(); - List orderedPostProcessorNames = new ArrayList(); - List nonOrderedPostProcessorNames = new ArrayList(); - for (String ppName : postProcessorNames) { - if (processedBeans.contains(ppName)) { - // skip - already processed in first phase above - } - else if (isTypeMatch(ppName, PriorityOrdered.class)) { - priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class)); - } - else if (isTypeMatch(ppName, Ordered.class)) { - orderedPostProcessorNames.add(ppName); - } - else { - nonOrderedPostProcessorNames.add(ppName); - } - } - - // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered. - OrderComparator.sort(priorityOrderedPostProcessors); - invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); - - // Next, invoke the BeanFactoryPostProcessors that implement Ordered. - List orderedPostProcessors = new ArrayList(); - for (String postProcessorName : orderedPostProcessorNames) { - orderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class)); - } - OrderComparator.sort(orderedPostProcessors); - invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); - - // Finally, invoke all other BeanFactoryPostProcessors. - List nonOrderedPostProcessors = new ArrayList(); - for (String postProcessorName : nonOrderedPostProcessorNames) { - nonOrderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class)); - } - invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory); - } - - /** - * Invoke the given BeanFactoryPostProcessor beans. - */ - private void invokeBeanFactoryPostProcessors( - Collection postProcessors, ConfigurableListableBeanFactory beanFactory) { - - for (BeanFactoryPostProcessor postProcessor : postProcessors) { - postProcessor.postProcessBeanFactory(beanFactory); - } + PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); } /** @@ -715,79 +615,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader *

Must be called before any instantiation of application beans. */ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) { - String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false); - - // Register BeanPostProcessorChecker that logs an info message when - // a bean is created during BeanPostProcessor instantiation, i.e. when - // a bean is not eligible for getting processed by all BeanPostProcessors. - int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length; - beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount)); - - // Separate between BeanPostProcessors that implement PriorityOrdered, - // Ordered, and the rest. - List priorityOrderedPostProcessors = new ArrayList(); - List internalPostProcessors = new ArrayList(); - List orderedPostProcessorNames = new ArrayList(); - List nonOrderedPostProcessorNames = new ArrayList(); - for (String ppName : postProcessorNames) { - if (isTypeMatch(ppName, PriorityOrdered.class)) { - BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); - priorityOrderedPostProcessors.add(pp); - if (pp instanceof MergedBeanDefinitionPostProcessor) { - internalPostProcessors.add(pp); - } - } - else if (isTypeMatch(ppName, Ordered.class)) { - orderedPostProcessorNames.add(ppName); - } - else { - nonOrderedPostProcessorNames.add(ppName); - } - } - - // First, register the BeanPostProcessors that implement PriorityOrdered. - OrderComparator.sort(priorityOrderedPostProcessors); - registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); - - // Next, register the BeanPostProcessors that implement Ordered. - List orderedPostProcessors = new ArrayList(); - for (String ppName : orderedPostProcessorNames) { - BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); - orderedPostProcessors.add(pp); - if (pp instanceof MergedBeanDefinitionPostProcessor) { - internalPostProcessors.add(pp); - } - } - OrderComparator.sort(orderedPostProcessors); - registerBeanPostProcessors(beanFactory, orderedPostProcessors); - - // Now, register all regular BeanPostProcessors. - List nonOrderedPostProcessors = new ArrayList(); - for (String ppName : nonOrderedPostProcessorNames) { - BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); - nonOrderedPostProcessors.add(pp); - if (pp instanceof MergedBeanDefinitionPostProcessor) { - internalPostProcessors.add(pp); - } - } - registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors); - - // Finally, re-register all internal BeanPostProcessors. - OrderComparator.sort(internalPostProcessors); - registerBeanPostProcessors(beanFactory, internalPostProcessors); - - beanFactory.addBeanPostProcessor(new ApplicationListenerDetector()); - } - - /** - * Register the given BeanPostProcessor beans. - */ - private void registerBeanPostProcessors( - ConfigurableListableBeanFactory beanFactory, List postProcessors) { - - for (BeanPostProcessor postProcessor : postProcessors) { - beanFactory.addBeanPostProcessor(postProcessor); - } + PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this); } /** @@ -1415,85 +1243,4 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader return sb.toString(); } - - /** - * BeanPostProcessor that logs an info message when a bean is created during - * BeanPostProcessor instantiation, i.e. when a bean is not eligible for - * getting processed by all BeanPostProcessors. - */ - private class BeanPostProcessorChecker implements BeanPostProcessor { - - private final ConfigurableListableBeanFactory beanFactory; - - private final int beanPostProcessorTargetCount; - - public BeanPostProcessorChecker(ConfigurableListableBeanFactory beanFactory, int beanPostProcessorTargetCount) { - this.beanFactory = beanFactory; - this.beanPostProcessorTargetCount = beanPostProcessorTargetCount; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) { - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) { - if (bean != null && !(bean instanceof BeanPostProcessor) && - this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) { - if (logger.isInfoEnabled()) { - logger.info("Bean '" + beanName + "' of type [" + bean.getClass() + - "] is not eligible for getting processed by all BeanPostProcessors " + - "(for example: not eligible for auto-proxying)"); - } - } - return bean; - } - } - - - /** - * BeanPostProcessor that detects beans which implement the ApplicationListener interface. - * This catches beans that can't reliably be detected by getBeanNamesForType. - */ - private class ApplicationListenerDetector implements MergedBeanDefinitionPostProcessor { - - private final Map singletonNames = new ConcurrentHashMap(64); - - @Override - public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { - if (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 (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 - addApplicationListener((ApplicationListener) bean); - } - else if (flag == null) { - if (logger.isWarnEnabled() && !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; - } - } - } 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 new file mode 100644 index 0000000000..f0b4a4b5a9 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java @@ -0,0 +1,370 @@ +/* + * Copyright 2002-2013 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.ArrayList; +import java.util.Collection; +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; + +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.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.OrderComparator; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; + +/** + * Delegate for AbstractApplicationContext's post-processor handling. + * + * @author Juergen Hoeller + * @since 4.0 + */ +class PostProcessorRegistrationDelegate { + + public static void invokeBeanFactoryPostProcessors( + ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) { + + // Invoke BeanDefinitionRegistryPostProcessors first, if any. + Set processedBeans = new HashSet(); + + if (beanFactory instanceof BeanDefinitionRegistry) { + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + List regularPostProcessors = new LinkedList(); + List registryPostProcessors = + new LinkedList(); + + for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) { + if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { + BeanDefinitionRegistryPostProcessor registryPostProcessor = + (BeanDefinitionRegistryPostProcessor) postProcessor; + registryPostProcessor.postProcessBeanDefinitionRegistry(registry); + registryPostProcessors.add(registryPostProcessor); + } + else { + regularPostProcessors.add(postProcessor); + } + } + + // Do not initialize FactoryBeans here: We need to leave all regular beans + // uninitialized to let the bean factory post-processors apply to them! + // Separate between BeanDefinitionRegistryPostProcessors that implement + // PriorityOrdered, Ordered, and the rest. + String[] postProcessorNames = + beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); + + // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered. + List priorityOrderedPostProcessors = new ArrayList(); + for (String ppName : postProcessorNames) { + if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { + priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); + processedBeans.add(ppName); + } + } + OrderComparator.sort(priorityOrderedPostProcessors); + registryPostProcessors.addAll(priorityOrderedPostProcessors); + invokeBeanDefinitionRegistryPostProcessors(priorityOrderedPostProcessors, registry); + + // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered. + postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); + List orderedPostProcessors = new ArrayList(); + for (String ppName : postProcessorNames) { + if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) { + orderedPostProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); + processedBeans.add(ppName); + } + } + OrderComparator.sort(orderedPostProcessors); + registryPostProcessors.addAll(orderedPostProcessors); + invokeBeanDefinitionRegistryPostProcessors(orderedPostProcessors, registry); + + // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear. + boolean reiterate = true; + while (reiterate) { + reiterate = false; + postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); + for (String ppName : postProcessorNames) { + if (!processedBeans.contains(ppName)) { + BeanDefinitionRegistryPostProcessor pp = beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class); + registryPostProcessors.add(pp); + processedBeans.add(ppName); + pp.postProcessBeanDefinitionRegistry(registry); + reiterate = true; + } + } + } + + // Now, invoke the postProcessBeanFactory callback of all processors handled so far. + invokeBeanFactoryPostProcessors(registryPostProcessors, beanFactory); + invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory); + } + + else { + // Invoke factory processors registered with the context instance. + invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory); + } + + // Do not initialize FactoryBeans here: We need to leave all regular beans + // uninitialized to let the bean factory post-processors apply to them! + String[] postProcessorNames = + beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); + + // Separate between BeanFactoryPostProcessors that implement PriorityOrdered, + // Ordered, and the rest. + List priorityOrderedPostProcessors = new ArrayList(); + List orderedPostProcessorNames = new ArrayList(); + List nonOrderedPostProcessorNames = new ArrayList(); + for (String ppName : postProcessorNames) { + if (processedBeans.contains(ppName)) { + // skip - already processed in first phase above + } + else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { + priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class)); + } + else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { + orderedPostProcessorNames.add(ppName); + } + else { + nonOrderedPostProcessorNames.add(ppName); + } + } + + // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered. + OrderComparator.sort(priorityOrderedPostProcessors); + invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); + + // Next, invoke the BeanFactoryPostProcessors that implement Ordered. + List orderedPostProcessors = new ArrayList(); + for (String postProcessorName : orderedPostProcessorNames) { + orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); + } + OrderComparator.sort(orderedPostProcessors); + invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); + + // Finally, invoke all other BeanFactoryPostProcessors. + List nonOrderedPostProcessors = new ArrayList(); + for (String postProcessorName : nonOrderedPostProcessorNames) { + nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); + } + invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory); + } + + public static void registerBeanPostProcessors( + ConfigurableListableBeanFactory beanFactory, ConfigurableApplicationContext applicationContext) { + + String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false); + + // Register BeanPostProcessorChecker that logs an info message when + // a bean is created during BeanPostProcessor instantiation, i.e. when + // a bean is not eligible for getting processed by all BeanPostProcessors. + int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length; + beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount)); + + // Separate between BeanPostProcessors that implement PriorityOrdered, + // Ordered, and the rest. + List priorityOrderedPostProcessors = new ArrayList(); + List internalPostProcessors = new ArrayList(); + List orderedPostProcessorNames = new ArrayList(); + List nonOrderedPostProcessorNames = new ArrayList(); + for (String ppName : postProcessorNames) { + if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { + BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); + priorityOrderedPostProcessors.add(pp); + if (pp instanceof MergedBeanDefinitionPostProcessor) { + internalPostProcessors.add(pp); + } + } + else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { + orderedPostProcessorNames.add(ppName); + } + else { + nonOrderedPostProcessorNames.add(ppName); + } + } + + // First, register the BeanPostProcessors that implement PriorityOrdered. + OrderComparator.sort(priorityOrderedPostProcessors); + registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); + + // Next, register the BeanPostProcessors that implement Ordered. + List orderedPostProcessors = new ArrayList(); + for (String ppName : orderedPostProcessorNames) { + BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); + orderedPostProcessors.add(pp); + if (pp instanceof MergedBeanDefinitionPostProcessor) { + internalPostProcessors.add(pp); + } + } + OrderComparator.sort(orderedPostProcessors); + registerBeanPostProcessors(beanFactory, orderedPostProcessors); + + // Now, register all regular BeanPostProcessors. + List nonOrderedPostProcessors = new ArrayList(); + for (String ppName : nonOrderedPostProcessorNames) { + BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); + nonOrderedPostProcessors.add(pp); + if (pp instanceof MergedBeanDefinitionPostProcessor) { + internalPostProcessors.add(pp); + } + } + registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors); + + // Finally, re-register all internal BeanPostProcessors. + OrderComparator.sort(internalPostProcessors); + registerBeanPostProcessors(beanFactory, internalPostProcessors); + + beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext)); + } + + /** + * Invoke the given BeanDefinitionRegistryPostProcessor beans. + */ + private static void invokeBeanDefinitionRegistryPostProcessors( + Collection postProcessors, BeanDefinitionRegistry registry) { + + for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { + postProcessor.postProcessBeanDefinitionRegistry(registry); + } + } + + /** + * Invoke the given BeanFactoryPostProcessor beans. + */ + private static void invokeBeanFactoryPostProcessors( + Collection postProcessors, ConfigurableListableBeanFactory beanFactory) { + + for (BeanFactoryPostProcessor postProcessor : postProcessors) { + postProcessor.postProcessBeanFactory(beanFactory); + } + } + + /** + * Register the given BeanPostProcessor beans. + */ + private static void registerBeanPostProcessors( + ConfigurableListableBeanFactory beanFactory, List postProcessors) { + + for (BeanPostProcessor postProcessor : postProcessors) { + beanFactory.addBeanPostProcessor(postProcessor); + } + } + + + /** + * BeanPostProcessor that logs an info message when a bean is created during + * BeanPostProcessor instantiation, i.e. when a bean is not eligible for + * getting processed by all BeanPostProcessors. + */ + private static class BeanPostProcessorChecker implements BeanPostProcessor { + + private static final Log logger = LogFactory.getLog(BeanPostProcessorChecker.class); + + private final ConfigurableListableBeanFactory beanFactory; + + private final int beanPostProcessorTargetCount; + + public BeanPostProcessorChecker(ConfigurableListableBeanFactory beanFactory, int beanPostProcessorTargetCount) { + this.beanFactory = beanFactory; + this.beanPostProcessorTargetCount = beanPostProcessorTargetCount; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean != null && !(bean instanceof BeanPostProcessor) && + this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) { + if (logger.isInfoEnabled()) { + logger.info("Bean '" + beanName + "' of type [" + bean.getClass() + + "] is not eligible for getting processed by all BeanPostProcessors " + + "(for example: not eligible for auto-proxying)"); + } + } + return bean; + } + } + + + /** + * BeanPostProcessor that detects beans which implement the ApplicationListener interface. + * This catches beans that can't reliably be detected by getBeanNamesForType. + */ + private static class ApplicationListenerDetector implements MergedBeanDefinitionPostProcessor { + + private static final Log logger = LogFactory.getLog(ApplicationListenerDetector.class); + + private final ConfigurableApplicationContext applicationContext; + + private final Map singletonNames = new ConcurrentHashMap(64); + + public ApplicationListenerDetector(ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { + if (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 (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; + } + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java index 2ce7ecab78..3deaf61d0c 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java @@ -21,15 +21,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; - import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotationMetadata; import org.springframework.stereotype.Component; @@ -38,25 +35,19 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** - * Tests for {@link Conditional} beans. + * Test for {@link Conditional} beans. * * @author Phillip Webb */ @SuppressWarnings("resource") public class ConfigurationClassWithConditionTests { - private final AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - @Rule public ExpectedException thrown = ExpectedException.none(); - @After - public void closeContext() { - ctx.close(); - } - @Test public void conditionalOnMissingBeanMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class); ctx.refresh(); assertTrue(ctx.containsBean("bean1")); @@ -66,6 +57,7 @@ public class ConfigurationClassWithConditionTests { @Test public void conditionalOnMissingBeanNoMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(BeanTwoConfiguration.class); ctx.refresh(); assertFalse(ctx.containsBean("bean1")); @@ -75,6 +67,7 @@ public class ConfigurationClassWithConditionTests { @Test public void conditionalOnBeanMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(BeanOneConfiguration.class, BeanThreeConfiguration.class); ctx.refresh(); assertTrue(ctx.containsBean("bean1")); @@ -83,6 +76,7 @@ public class ConfigurationClassWithConditionTests { @Test public void conditionalOnBeanNoMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(BeanThreeConfiguration.class); ctx.refresh(); assertFalse(ctx.containsBean("bean1")); @@ -91,6 +85,7 @@ public class ConfigurationClassWithConditionTests { @Test public void metaConditional() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(ConfigurationWithMetaCondition.class); ctx.refresh(); assertTrue(ctx.containsBean("bean")); @@ -98,6 +93,7 @@ public class ConfigurationClassWithConditionTests { @Test public void nonConfigurationClass() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(NonConfigurationClass.class); ctx.refresh(); thrown.expect(NoSuchBeanDefinitionException.class); @@ -106,6 +102,7 @@ public class ConfigurationClassWithConditionTests { @Test public void methodConditional() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(ConditionOnMethodConfiguration.class); ctx.refresh(); thrown.expect(NoSuchBeanDefinitionException.class); @@ -119,25 +116,6 @@ public class ConfigurationClassWithConditionTests { ctx.refresh(); } - @Test - public void importsNotLoaded() throws Exception { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - ctx.register(ImportsNotLoaded.class); - ctx.refresh(); - assertThat(ctx.containsBeanDefinition("a"), equalTo(false)); - assertThat(ctx.containsBeanDefinition("b"), equalTo(false)); - } - - @Test - public void sensibleConditionContext() throws Exception { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - ctx.setResourceLoader(new DefaultResourceLoader()); - ctx.setClassLoader(getClass().getClassLoader()); - ctx.register(SensibleConditionContext.class); - ctx.refresh(); - assertThat(ctx.getBean(ExampleBean.class), instanceOf(ExampleBean.class)); - } - @Configuration static class BeanOneConfiguration { @Bean @@ -279,70 +257,6 @@ public class ConfigurationClassWithConditionTests { } - @Configuration - @Never - @Import({ ConfigurationNotLoaded.class, RegistrarNotLoaded.class, ImportSelectorNotLoaded.class }) - static class ImportsNotLoaded { - static { - if (true) throw new RuntimeException(); - } - } - - @Configuration - static class ConfigurationNotLoaded { - @Bean - public String a() { - return "a"; - } - } - - static class RegistrarNotLoaded implements ImportBeanDefinitionRegistrar { - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, - BeanDefinitionRegistry registry) { - throw new RuntimeException(); - } - } - - static class ImportSelectorNotLoaded implements ImportSelector { - - @Override - public String[] selectImports(AnnotationMetadata importingClassMetadata) { - return new String[] { SelectedConfigurationNotLoaded.class.getName() }; - } - - } - - @Configuration - static class SelectedConfigurationNotLoaded { - @Bean - public String b() { - return "b"; - } - } - - @Configuration - @Conditional(SensibleConditionContextCondition.class) - static class SensibleConditionContext { - @Bean - ExampleBean exampleBean() { - return new ExampleBean(); - } - } - - static class SensibleConditionContextCondition implements Condition { - - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - assertThat(context.getBeanFactory(), notNullValue()); - assertThat(context.getClassLoader(), notNullValue()); - assertThat(context.getEnvironment(), notNullValue()); - assertThat(context.getRegistry(), notNullValue()); - assertThat(context.getResourceLoader(), notNullValue()); - return true; - } - } - static class ExampleBean { } 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 152ecfb6ec..b68aef069d 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 @@ -16,19 +16,22 @@ package org.springframework.context.support; -import static org.junit.Assert.*; - import org.junit.Test; + +import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; -import org.springframework.tests.sample.beans.TestBean; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; +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.RootBeanDefinition; import org.springframework.context.ApplicationContext; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.PriorityOrdered; +import org.springframework.tests.sample.beans.TestBean; + +import static org.junit.Assert.*; /** * Tests the interaction between {@link ApplicationContext} implementations and @@ -93,6 +96,29 @@ public class BeanFactoryPostProcessorTests { assertFalse(bfpp.wasCalled); } + @Test + public void testBeanDefinitionRegistryPostProcessor() throws Exception { + StaticApplicationContext ac = new StaticApplicationContext(); + ac.registerSingleton("tb1", TestBean.class); + ac.registerSingleton("tb2", TestBean.class); + TestBeanDefinitionRegistryPostProcessor bdrpp = new TestBeanDefinitionRegistryPostProcessor(); + ac.addBeanFactoryPostProcessor(bdrpp); + assertFalse(bdrpp.wasCalled); + ac.refresh(); + assertTrue(bdrpp.wasCalled); + assertTrue(ac.getBean(TestBeanFactoryPostProcessor.class).wasCalled); + } + + @Test + public void testBeanDefinitionRegistryPostProcessorRegisteringAnother() throws Exception { + StaticApplicationContext ac = new StaticApplicationContext(); + ac.registerSingleton("tb1", TestBean.class); + ac.registerSingleton("tb2", TestBean.class); + ac.registerBeanDefinition("bdrpp2", new RootBeanDefinition(TestBeanDefinitionRegistryPostProcessor2.class)); + ac.refresh(); + assertTrue(ac.getBean(TestBeanFactoryPostProcessor.class).wasCalled); + } + public static class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @@ -110,4 +136,38 @@ public class BeanFactoryPostProcessorTests { } } + + public static class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { + + public boolean wasCalled; + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + registry.registerBeanDefinition("bfpp", new RootBeanDefinition(TestBeanFactoryPostProcessor.class)); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + this.wasCalled = true; + } + } + + + public static class TestBeanDefinitionRegistryPostProcessor2 implements BeanDefinitionRegistryPostProcessor, PriorityOrdered { + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + registry.registerBeanDefinition("anotherpp", new RootBeanDefinition(TestBeanDefinitionRegistryPostProcessor.class)); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + } + + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE; + } + } + }