From 6b2b5c4c233bba63fa5da2b11146ffaf66f9de80 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 31 Jan 2010 14:05:28 +0000 Subject: [PATCH] introduced BeanDefinitionRegistryPostProcessor extension to BeanFactoryPostProcessor; @Configuration classes support definition of BeanFactoryPostProcessor beans as well (SPR-6455, SPR-6611) --- ...onfigurationClassBeanDefinitionReader.java | 31 ++++++---- .../ConfigurationClassPostProcessor.java | 24 ++++---- .../support/AbstractApplicationContext.java | 19 ++++++- .../ConfigurationClassProcessingTests.java | 57 ++++++++++++++++++- .../configuration/ImportResourceTests.java | 22 ++++--- .../configuration/ImportXmlConfig-context.xml | 8 +++ 6 files changed, 128 insertions(+), 33 deletions(-) diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index ccd66d6534..032b3484af 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -27,6 +27,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; @@ -48,7 +49,6 @@ import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.StandardAnnotationMetadata; -import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.stereotype.Component; @@ -68,11 +68,11 @@ import org.springframework.util.StringUtils; */ class ConfigurationClassBeanDefinitionReader { - static final String CONFIGURATION_CLASS_FULL = "full"; + private static final String CONFIGURATION_CLASS_FULL = "full"; - static final String CONFIGURATION_CLASS_LITE = "lite"; + private static final String CONFIGURATION_CLASS_LITE = "lite"; - static final String CONFIGURATION_CLASS_ATTRIBUTE = + private static final String CONFIGURATION_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); private static final Log logger = LogFactory.getLog(ConfigurationClassBeanDefinitionReader.class); @@ -86,7 +86,6 @@ class ConfigurationClassBeanDefinitionReader { private final MetadataReaderFactory metadataReaderFactory; - /** * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used * to populate the given {@link BeanDefinitionRegistry}. @@ -95,6 +94,7 @@ class ConfigurationClassBeanDefinitionReader { */ public ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor, ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory) { + this.registry = registry; this.sourceExtractor = sourceExtractor; this.problemReporter = problemReporter; @@ -145,13 +145,15 @@ class ConfigurationClassBeanDefinitionReader { if (logger.isDebugEnabled()) { logger.debug(String.format("Registered bean definition for imported @Configuration class %s", configBeanName)); } - } else { + } + else { try { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); AnnotationMetadata metadata = reader.getAnnotationMetadata(); this.problemReporter.error( new InvalidConfigurationImportProblem(className, reader.getResource(), metadata)); - } catch (IOException ex) { + } + catch (IOException ex) { throw new IllegalStateException("Could not create MetadataReader for class " + className); } } @@ -181,7 +183,7 @@ class ConfigurationClassBeanDefinitionReader { this.registry.registerAlias(beanName, alias); } - // has this already been overriden (i.e.: via XML)? + // has this already been overridden (e.g. via XML)? if (this.registry.containsBeanDefinition(beanName)) { BeanDefinition existingBeanDef = registry.getBeanDefinition(beanName); // is the existing bean definition one that was created from a configuration class? @@ -277,6 +279,7 @@ class ConfigurationClassBeanDefinitionReader { } } + /** * Check whether the given bean definition is a candidate for a configuration class, * and mark it accordingly. @@ -284,7 +287,7 @@ class ConfigurationClassBeanDefinitionReader { * @param metadataReaderFactory the current factory in use by the caller * @return whether the candidate qualifies as (any kind of) configuration class */ - static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { + public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { AnnotationMetadata metadata = null; // Check already loaded Class if present... @@ -322,6 +325,14 @@ class ConfigurationClassBeanDefinitionReader { return false; } + /** + * Determine whether the given bean definition indicates a full @Configuration class. + */ + public static boolean isFullConfigurationClass(BeanDefinition beanDef) { + return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)); + } + + /** * {@link RootBeanDefinition} marker subclass used to signify that a bean definition * created from a configuration class as opposed to any other configuration source. @@ -329,7 +340,7 @@ class ConfigurationClassBeanDefinitionReader { * definition was created externally. */ @SuppressWarnings("serial") - private class ConfigurationClassBeanDefinition extends RootBeanDefinition implements AnnotatedBeanDefinition { + private static class ConfigurationClassBeanDefinition extends RootBeanDefinition implements AnnotatedBeanDefinition { private AnnotationMetadata annotationMetadata; diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 17f1da85ad..39c017f8b9 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -16,9 +16,6 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.CONFIGURATION_CLASS_ATTRIBUTE; -import static org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.CONFIGURATION_CLASS_FULL; - import java.io.IOException; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -40,6 +37,7 @@ import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.SourceExtractor; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -63,7 +61,7 @@ import org.springframework.util.ClassUtils; * @author Juergen Hoeller * @since 3.0 */ -public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor, BeanClassLoaderAware { +public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanClassLoaderAware { /** Whether the CGLIB2 library is present on the classpath */ private static final boolean cglibAvailable = ClassUtils.isPresent( @@ -124,16 +122,18 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor } + /** + * Derive further bean definitions from the configuration classes in the registry. + */ + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { + processConfigBeanDefinitions(registry); + } + /** * Prepare the Configuration classes for servicing bean requests at runtime * by replacing them with CGLIB-enhanced subclasses. */ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - if (!(beanFactory instanceof BeanDefinitionRegistry)) { - throw new IllegalStateException( - "ConfigurationClassPostProcessor expects a BeanFactory that implements BeanDefinitionRegistry"); - } - processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); enhanceConfigurationClasses(beanFactory); } @@ -174,8 +174,8 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor parser.validate(); // Read the model and create bean definitions based on its content - ConfigurationClassBeanDefinitionReader reader = - new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory); + ConfigurationClassBeanDefinitionReader reader = new ConfigurationClassBeanDefinitionReader( + registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory); reader.loadBeanDefinitions(parser.getConfigurationClasses()); } @@ -189,7 +189,7 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor Map configBeanDefs = new LinkedHashMap(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); - if (CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))) { + if (ConfigurationClassBeanDefinitionReader.isFullConfigurationClass(beanDef)) { if (!(beanDef instanceof AbstractBeanDefinition)) { throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" + beanName + "' since it is not stored in an AbstractBeanDefinition subclass"); diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index c614b5d5a4..e94f070118 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 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. @@ -39,6 +39,8 @@ 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.support.ResourceEditorRegistrar; import org.springframework.context.ApplicationContext; @@ -569,6 +571,21 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader *

Must be called before singleton instantiation. */ protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { + // Invoke BeanDefinitionRegistryPostProcessors first, if any. + if (beanFactory instanceof BeanDefinitionRegistry) { + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + for (BeanFactoryPostProcessor postProcessor : getBeanFactoryPostProcessors()) { + if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { + ((BeanDefinitionRegistryPostProcessor) postProcessor).postProcessBeanDefinitionRegistry(registry); + } + } + Collection registryPostProcessors = + beanFactory.getBeansOfType(BeanDefinitionRegistryPostProcessor.class, true, false).values(); + for (BeanDefinitionRegistryPostProcessor postProcessor : registryPostProcessors) { + postProcessor.postProcessBeanDefinitionRegistry(registry); + } + } + // Invoke factory processors registered with the context instance. invokeBeanFactoryPostProcessors(getBeanFactoryPostProcessors(), beanFactory); diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index 5c005e6fed..a0f734177e 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 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. @@ -26,9 +26,13 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; 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.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -58,6 +62,7 @@ public class ConfigurationClassProcessingTests { factory.registerBeanDefinition(configBeanName, new RootBeanDefinition(configClass)); } ConfigurationClassPostProcessor ccpp = new ConfigurationClassPostProcessor(); + ccpp.postProcessBeanDefinitionRegistry(factory); ccpp.postProcessBeanFactory(factory); RequiredAnnotationBeanPostProcessor rapp = new RequiredAnnotationBeanPostProcessor(); rapp.setBeanFactory(factory); @@ -123,6 +128,19 @@ public class ConfigurationClassProcessingTests { assertNotSame(bar.getSpouse(), baz); } + @Test + public void configurationWithPostProcessor() { + BeanFactory factory = new AnnotationConfigApplicationContext(ConfigWithPostProcessor.class); + + TestBean foo = factory.getBean("foo", TestBean.class); + ITestBean bar = factory.getBean("bar", ITestBean.class); + ITestBean baz = factory.getBean("baz", ITestBean.class); + + assertEquals("foo-processed", foo.getName()); + assertEquals("bar-processed", bar.getName()); + assertEquals("baz-processed", baz.getName()); + } + @Configuration static class ConfigWithBeanWithCustomName { @@ -152,7 +170,9 @@ public class ConfigurationClassProcessingTests { @Configuration static class ConfigWithBeanWithAliases { + static TestBean testBean = new TestBean(); + @Bean(name={"name1", "alias1", "alias2", "alias3"}) public TestBean methodName() { return testBean; @@ -177,7 +197,40 @@ public class ConfigurationClassProcessingTests { @Bean @Scope("prototype") public TestBean baz() { - return new TestBean("bar"); + return new TestBean("baz"); + } + } + + + static class ConfigWithPostProcessor extends ConfigWithPrototypeBean { + + @Bean + public BeanPostProcessor beanPostProcessor() { + return new BeanPostProcessor() { + String nameSuffix; + public void setNameSuffix(String nameSuffix) { + this.nameSuffix = nameSuffix; + } + public Object postProcessBeforeInitialization(Object bean, String beanName) { + if (bean instanceof ITestBean) { + ((ITestBean) bean).setName(((ITestBean) bean).getName() + nameSuffix); + } + return bean; + } + public Object postProcessAfterInitialization(Object bean, String beanName) { + return bean; + } + }; + } + + @Bean + public BeanFactoryPostProcessor beanFactoryPostProcessor() { + return new BeanFactoryPostProcessor() { + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + BeanDefinition bd = beanFactory.getBeanDefinition("beanPostProcessor"); + bd.getPropertyValues().addPropertyValue("nameSuffix", "-processed"); + } + }; } } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportResourceTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportResourceTests.java index 0204a757b0..745cd792fb 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportResourceTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportResourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 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. @@ -16,16 +16,17 @@ package org.springframework.context.annotation.configuration; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; import org.junit.Ignore; import org.junit.Test; +import test.beans.TestBean; + import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ApplicationContext; @@ -34,12 +35,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; -import test.beans.TestBean; - /** * Integration tests for {@link ImportResource} support. * * @author Chris Beams + * @author Juergen Hoeller */ public class ImportResourceTests { @Test @@ -47,13 +47,17 @@ public class ImportResourceTests { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlConfig.class); assertTrue("did not contain java-declared bean", ctx.containsBean("javaDeclaredBean")); assertTrue("did not contain xml-declared bean", ctx.containsBean("xmlDeclaredBean")); + TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class); + assertEquals("myName", tb.getName()); } @Configuration @ImportResource("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml") static class ImportXmlConfig { + @Value("${name}") + private String name; public @Bean TestBean javaDeclaredBean() { - return new TestBean("java.declared"); + return new TestBean(this.name); } } @@ -63,6 +67,8 @@ public class ImportResourceTests { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithRelativePathConfig.class); assertTrue("did not contain java-declared bean", ctx.containsBean("javaDeclaredBean")); assertTrue("did not contain xml-declared bean", ctx.containsBean("xmlDeclaredBean")); + TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class); + assertEquals("myName", tb.getName()); } @Configuration diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml index c838e5540f..68d5f0286d 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml @@ -7,4 +7,12 @@ + + + + + + + +