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.
This commit is contained in:
parent
89005a5b70
commit
cdb01cbd37
|
|
@ -19,6 +19,7 @@ package org.springframework.context.annotation;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -62,7 +63,7 @@ class ConfigurationClassParser {
|
||||||
|
|
||||||
private final ProblemReporter problemReporter;
|
private final ProblemReporter problemReporter;
|
||||||
|
|
||||||
private final Stack<ConfigurationClass> importStack = new ImportStack();
|
private final ImportStack importStack = new ImportStack();
|
||||||
|
|
||||||
private final Set<ConfigurationClass> configurationClasses =
|
private final Set<ConfigurationClass> configurationClasses =
|
||||||
new LinkedHashSet<ConfigurationClass>();
|
new LinkedHashSet<ConfigurationClass>();
|
||||||
|
|
@ -168,6 +169,7 @@ class ConfigurationClassParser {
|
||||||
else {
|
else {
|
||||||
this.importStack.push(configClass);
|
this.importStack.push(configClass);
|
||||||
for (String classToImport : classesToImport) {
|
for (String classToImport : classesToImport) {
|
||||||
|
this.importStack.registerImport(configClass.getMetadata().getClassName(), classToImport);
|
||||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(classToImport);
|
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(classToImport);
|
||||||
processConfigurationClass(new ConfigurationClass(reader, null));
|
processConfigurationClass(new ConfigurationClass(reader, null));
|
||||||
}
|
}
|
||||||
|
|
@ -189,9 +191,28 @@ class ConfigurationClassParser {
|
||||||
return this.configurationClasses;
|
return this.configurationClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImportRegistry getImportRegistry() {
|
||||||
|
return this.importStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface ImportRegistry {
|
||||||
|
String getImportingClassFor(String importedClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
private static class ImportStack extends Stack<ConfigurationClass> {
|
private static class ImportStack extends Stack<ConfigurationClass> implements ImportRegistry {
|
||||||
|
|
||||||
|
private Map<String, String> imports = new HashMap<String, String>();
|
||||||
|
|
||||||
|
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}
|
* Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
|
||||||
|
|
|
||||||
|
|
@ -24,27 +24,38 @@ import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
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.BeanDefinition;
|
||||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||||
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.ConfigurableListableBeanFactory;
|
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.FailFastProblemReporter;
|
||||||
import org.springframework.beans.factory.parsing.PassThroughSourceExtractor;
|
import org.springframework.beans.factory.parsing.PassThroughSourceExtractor;
|
||||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||||
import org.springframework.beans.factory.parsing.SourceExtractor;
|
import org.springframework.beans.factory.parsing.SourceExtractor;
|
||||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
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.BeanDefinitionRegistry;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
import org.springframework.context.EnvironmentAware;
|
import org.springframework.context.EnvironmentAware;
|
||||||
import org.springframework.context.ResourceLoaderAware;
|
import org.springframework.context.ResourceLoaderAware;
|
||||||
|
import org.springframework.context.annotation.ConfigurationClassParser.ImportRegistry;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.PriorityOrdered;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.core.io.DefaultResourceLoader;
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
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.CachingMetadataReaderFactory;
|
||||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||||
|
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
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.
|
* Derive further bean definitions from the configuration classes in the registry.
|
||||||
*/
|
*/
|
||||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
|
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
|
||||||
|
BeanDefinitionReaderUtils.registerWithGeneratedName(new RootBeanDefinition(ImportAwareBeanPostProcessor.class), registry);
|
||||||
if (this.postProcessBeanDefinitionRegistryCalled) {
|
if (this.postProcessBeanDefinitionRegistryCalled) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"postProcessBeanDefinitionRegistry already called for this post-processor");
|
"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
|
// Read the model and create bean definitions based on its content
|
||||||
reader.loadBeanDefinitions(parser.getConfigurationClasses());
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<String, Object> 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<String, Object> 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()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -193,8 +193,8 @@ public class ScopingTests {
|
||||||
@Test
|
@Test
|
||||||
public void testScopedConfigurationBeanDefinitionCount() throws Exception {
|
public void testScopedConfigurationBeanDefinitionCount() throws Exception {
|
||||||
// count the beans
|
// count the beans
|
||||||
// 6 @Beans + 1 Configuration + 2 scoped proxy
|
// 6 @Beans + 1 Configuration + 2 scoped proxy + 1 importRegistry
|
||||||
assertEquals(9, ctx.getBeanDefinitionCount());
|
assertEquals(10, ctx.getBeanDefinitionCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue