From cdb01cbd3795f273b751d0f0a45caa22d07c62da Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Fri, 6 May 2011 19:05:42 +0000 Subject: [PATCH] Introduce ImportAware interface @Configuration classes may implement ImportAware in order to be injected with the AnnotationMetadata of their @Import'ing class. Includes the introduction of a new PriorityOrdered ImportAwareBeanPostProcessor that handles injection of the importing class metadata. --- .../annotation/ConfigurationClassParser.java | 25 +++- .../ConfigurationClassPostProcessor.java | 56 +++++++ .../context/annotation/ImportAware.java | 38 +++++ .../context/annotation/ImportAwareTests.java | 137 ++++++++++++++++++ .../configuration/ScopingTests.java | 4 +- 5 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 org.springframework.context/src/main/java/org/springframework/context/annotation/ImportAware.java create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 6d33e5bd5b0..112200cf75a 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -19,6 +19,7 @@ package org.springframework.context.annotation; import java.io.IOException; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -62,7 +63,7 @@ class ConfigurationClassParser { private final ProblemReporter problemReporter; - private final Stack importStack = new ImportStack(); + private final ImportStack importStack = new ImportStack(); private final Set configurationClasses = new LinkedHashSet(); @@ -168,6 +169,7 @@ class ConfigurationClassParser { else { this.importStack.push(configClass); for (String classToImport : classesToImport) { + this.importStack.registerImport(configClass.getMetadata().getClassName(), classToImport); MetadataReader reader = this.metadataReaderFactory.getMetadataReader(classToImport); processConfigurationClass(new ConfigurationClass(reader, null)); } @@ -189,9 +191,28 @@ class ConfigurationClassParser { return this.configurationClasses; } + public ImportRegistry getImportRegistry() { + return this.importStack; + } + + + interface ImportRegistry { + String getImportingClassFor(String importedClass); + } + @SuppressWarnings("serial") - private static class ImportStack extends Stack { + private static class ImportStack extends Stack implements ImportRegistry { + + private Map imports = new HashMap(); + + public String getImportingClassFor(String importedClass) { + return imports.get(importedClass); + } + + public void registerImport(String importingClass, String importedClass) { + imports.put(importedClass, importingClass); + } /** * Simplified contains() implementation that tests to see if any {@link ConfigurationClass} 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 01cbd8ee40a..f7a3cede274 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 @@ -24,27 +24,38 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; 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.SingletonBeanRegistry; import org.springframework.beans.factory.parsing.FailFastProblemReporter; import org.springframework.beans.factory.parsing.PassThroughSourceExtractor; 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.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.annotation.ConfigurationClassParser.ImportRegistry; import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; import org.springframework.core.env.Environment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -147,6 +158,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo * Derive further bean definitions from the configuration classes in the registry. */ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { + BeanDefinitionReaderUtils.registerWithGeneratedName(new RootBeanDefinition(ImportAwareBeanPostProcessor.class), registry); if (this.postProcessBeanDefinitionRegistryCalled) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called for this post-processor"); @@ -231,6 +243,13 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo // Read the model and create bean definitions based on its content reader.loadBeanDefinitions(parser.getConfigurationClasses()); + + // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes + if (registry instanceof SingletonBeanRegistry) { + if (!((SingletonBeanRegistry) registry).containsSingleton("importRegistry")) { + ((SingletonBeanRegistry) registry).registerSingleton("importRegistry", parser.getImportRegistry()); + } + } } /** @@ -278,4 +297,41 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo } } + + private static class ImportAwareBeanPostProcessor implements PriorityOrdered, BeanFactoryAware, BeanPostProcessor { + + private BeanFactory beanFactory; + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof ImportAware) { + ImportRegistry importRegistry = beanFactory.getBean(ImportRegistry.class); + String importingClass = importRegistry.getImportingClassFor(bean.getClass().getSuperclass().getName()); + if (importingClass != null) { + try { + AnnotationMetadata metadata = new SimpleMetadataReaderFactory().getMetadataReader(importingClass).getAnnotationMetadata(); + ((ImportAware) bean).setImportMetadata(metadata); + } catch (IOException ex) { + // should never occur -> at this point we know the class is present anyway + throw new IllegalStateException(ex); + } + } + else { + // no importing class was found + } + } + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportAware.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportAware.java new file mode 100644 index 00000000000..6daf4dddea6 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportAware.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2011 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.annotation; + +import org.springframework.beans.factory.Aware; +import org.springframework.core.type.AnnotationMetadata; + +/** + * Interface to be implemented by any @{@link Configuration} class that wishes + * to be injected with the {@link AnnotationMetadata} of the @{@code Configuration} + * class that imported it. Useful in conjunction with annotations that + * use @{@link Import} as a meta-annotation. + * + * @author Chris Beams + * @since 3.1 + */ +public interface ImportAware extends Aware { + + /** + * Set the annotation metadata of the importing @{@code Configuration} class. + */ + void setImportMetadata(AnnotationMetadata importMetadata); + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java new file mode 100644 index 00000000000..62a22cbf5c3 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java @@ -0,0 +1,137 @@ +/* + * Copyright 2002-2011 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.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Map; + +import org.junit.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor; + +/** + * Tests that an ImportAware @Configuration classes gets injected with the + * annotation metadata of the @Configuration class that imported it. + * + * @author Chris Beams + * @since 3.1 + */ +public class ImportAwareTests { + + @Test + public void directlyAnnotatedWithImport() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ImportingConfig.class); + ctx.refresh(); + + ctx.getBean("importedConfigBean"); + + ImportedConfig importAwareConfig = ctx.getBean(ImportedConfig.class); + AnnotationMetadata importMetadata = importAwareConfig.importMetadata; + assertThat("import metadata was not injected", importMetadata, notNullValue()); + assertThat(importMetadata.getClassName(), is(ImportingConfig.class.getName())); + Map importAttribs = importMetadata.getAnnotationAttributes(Import.class.getName()); + Class[] importedClasses = (Class[])importAttribs.get("value"); + assertThat(importedClasses[0].getName(), is(ImportedConfig.class.getName())); + } + + @Test + public void indirectlyAnnotatedWithImport() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(IndirectlyImportingConfig.class); + ctx.refresh(); + + ctx.getBean("importedConfigBean"); + + ImportedConfig importAwareConfig = ctx.getBean(ImportedConfig.class); + AnnotationMetadata importMetadata = importAwareConfig.importMetadata; + assertThat("import metadata was not injected", importMetadata, notNullValue()); + assertThat(importMetadata.getClassName(), is(IndirectlyImportingConfig.class.getName())); + Map enableAttribs = importMetadata.getAnnotationAttributes(EnableImportedConfig.class.getName()); + String foo = (String)enableAttribs.get("foo"); + assertThat(foo, is("xyz")); + } + + + @Configuration + @Import(ImportedConfig.class) + static class ImportingConfig { + } + + @Configuration + @EnableImportedConfig(foo="xyz") + static class IndirectlyImportingConfig { + } + + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Import(ImportedConfig.class) + public @interface EnableImportedConfig { + String foo() default ""; + } + + + @Configuration + static class ImportedConfig implements ImportAware { + + AnnotationMetadata importMetadata; + + public void setImportMetadata(AnnotationMetadata importMetadata) { + this.importMetadata = importMetadata; + } + + @Bean + public BPP importedConfigBean() { + return new BPP(); + } + + @Bean + public AsyncAnnotationBeanPostProcessor asyncBPP() { + return new AsyncAnnotationBeanPostProcessor(); + } + } + + + static class BPP implements BeanFactoryAware, BeanPostProcessor { + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + // TODO Auto-generated method stub + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + // TODO Auto-generated method stub + return bean; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + System.out.println("ImportAwareTests.BPP.setBeanFactory()"); + } + } +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java index 01c9331a911..c0d14156366 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java @@ -193,8 +193,8 @@ public class ScopingTests { @Test public void testScopedConfigurationBeanDefinitionCount() throws Exception { // count the beans - // 6 @Beans + 1 Configuration + 2 scoped proxy - assertEquals(9, ctx.getBeanDefinitionCount()); + // 6 @Beans + 1 Configuration + 2 scoped proxy + 1 importRegistry + assertEquals(10, ctx.getBeanDefinitionCount()); } // /**