Add support for DeferredImportSelector
Add DeferredImportSelector interface that can be used to select imports after all @Configuration beans have been processed.
This commit is contained in:
@@ -25,6 +25,8 @@ import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
@@ -32,19 +34,23 @@ import java.util.Stack;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.Aware;
|
||||
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.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReader;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.NestedIOException;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
@@ -82,6 +88,15 @@ import static org.springframework.context.annotation.MetadataUtils.*;
|
||||
*/
|
||||
class ConfigurationClassParser {
|
||||
|
||||
private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
|
||||
new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() {
|
||||
public int compare(DeferredImportSelectorHolder o1,
|
||||
DeferredImportSelectorHolder o2) {
|
||||
return AnnotationAwareOrderComparator.INSTANCE.compare(
|
||||
o1.getImportSelector(), o2.getImportSelector());
|
||||
}
|
||||
};
|
||||
|
||||
private final MetadataReaderFactory metadataReaderFactory;
|
||||
|
||||
private final ProblemReporter problemReporter;
|
||||
@@ -106,6 +121,8 @@ class ConfigurationClassParser {
|
||||
|
||||
private final BeanNameGenerator beanNameGenerator;
|
||||
|
||||
private final List<DeferredImportSelectorHolder> deferredImportSelectors =
|
||||
new LinkedList<DeferredImportSelectorHolder>();
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClassParser} instance that will be used
|
||||
@@ -125,6 +142,23 @@ class ConfigurationClassParser {
|
||||
resourceLoader, environment, componentScanBeanNameGenerator, registry);
|
||||
}
|
||||
|
||||
public void parse(Set<BeanDefinitionHolder> configCandidates) {
|
||||
for (BeanDefinitionHolder holder : configCandidates) {
|
||||
BeanDefinition bd = holder.getBeanDefinition();
|
||||
try {
|
||||
if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
|
||||
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
|
||||
}
|
||||
else {
|
||||
parse(bd.getBeanClassName(), holder.getBeanName());
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
|
||||
}
|
||||
}
|
||||
processDeferredImportSelectors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the specified {@link Configuration @Configuration} class.
|
||||
@@ -142,7 +176,7 @@ class ConfigurationClassParser {
|
||||
* @param clazz the Class to parse
|
||||
* @param beanName must not be null (as of Spring 3.1.1)
|
||||
*/
|
||||
public void parse(Class<?> clazz, String beanName) throws IOException {
|
||||
protected void parse(Class<?> clazz, String beanName) throws IOException {
|
||||
processConfigurationClass(new ConfigurationClass(clazz, beanName));
|
||||
}
|
||||
|
||||
@@ -369,6 +403,21 @@ class ConfigurationClassParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void processDeferredImportSelectors() {
|
||||
Collections.sort(this.deferredImportSelectors, DEFERRED_IMPORT_COMPARATOR);
|
||||
for (DeferredImportSelectorHolder deferredImport : this.deferredImportSelectors) {
|
||||
try {
|
||||
ConfigurationClass configClass = deferredImport.getConfigurationClass();
|
||||
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
|
||||
processImport(configClass, Arrays.asList(imports), false);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load bean class: ", ex);
|
||||
}
|
||||
}
|
||||
deferredImportSelectors.clear();
|
||||
}
|
||||
|
||||
private void processImport(ConfigurationClass configClass, Collection<?> classesToImport, boolean checkForCircularImports) throws IOException {
|
||||
if (checkForCircularImports && this.importStack.contains(configClass)) {
|
||||
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata()));
|
||||
@@ -384,7 +433,12 @@ class ConfigurationClassParser {
|
||||
Class<?> candidateClass = (candidate instanceof Class ? (Class) candidate : this.resourceLoader.getClassLoader().loadClass((String) candidate));
|
||||
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
|
||||
invokeAwareMethods(selector);
|
||||
processImport(configClass, Arrays.asList(selector.selectImports(importingClassMetadata)), false);
|
||||
if(selector instanceof DeferredImportSelector) {
|
||||
this.deferredImportSelectors.add(new DeferredImportSelectorHolder(
|
||||
configClass, (DeferredImportSelector) selector));
|
||||
} else {
|
||||
processImport(configClass, Arrays.asList(selector.selectImports(importingClassMetadata)), false);
|
||||
}
|
||||
}
|
||||
else if (checkAssignability(ImportBeanDefinitionRegistrar.class, candidateToCheck)) {
|
||||
// the candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
|
||||
@@ -539,4 +593,24 @@ class ConfigurationClassParser {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class DeferredImportSelectorHolder {
|
||||
|
||||
private ConfigurationClass configurationClass;
|
||||
|
||||
private DeferredImportSelector importSelector;
|
||||
|
||||
public DeferredImportSelectorHolder(ConfigurationClass configurationClass, DeferredImportSelector importSelector) {
|
||||
this.configurationClass = configurationClass;
|
||||
this.importSelector = importSelector;
|
||||
}
|
||||
|
||||
public ConfigurationClass getConfigurationClass() {
|
||||
return configurationClass;
|
||||
}
|
||||
|
||||
public DeferredImportSelector getImportSelector() {
|
||||
return importSelector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,20 +276,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
ConfigurationClassParser parser = new ConfigurationClassParser(
|
||||
this.metadataReaderFactory, this.problemReporter, this.environment,
|
||||
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
|
||||
for (BeanDefinitionHolder holder : configCandidates) {
|
||||
BeanDefinition bd = holder.getBeanDefinition();
|
||||
try {
|
||||
if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
|
||||
parser.parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
|
||||
}
|
||||
else {
|
||||
parser.parse(bd.getBeanClassName(), holder.getBeanName());
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
|
||||
}
|
||||
}
|
||||
parser.parse(configCandidates);
|
||||
parser.validate();
|
||||
|
||||
// Handle any @PropertySource annotations
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
/**
|
||||
* A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans
|
||||
* have been processed. This type of selector can be particularly useful when the selected
|
||||
* imports are {@code @Conditional}.
|
||||
*
|
||||
* <p>Implementations can also extend the {@link org.springframework.core.Ordered}
|
||||
* interface or use the {@link org.springframework.core.annotation.Order} annotation to
|
||||
* indicate a precedence against other {@link DeferredImportSelector}s.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface DeferredImportSelector extends ImportSelector {
|
||||
|
||||
}
|
||||
@@ -32,8 +32,14 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>ImportSelectors are usually processed in the same way as regular {@code @Import}
|
||||
* annotations, however, it is also possible to defer selection of imports until all
|
||||
* {@code @Configuration} classes have been processed (see {@link DeferredImportSelector}
|
||||
* for details).
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.1
|
||||
* @see DeferredImportSelector
|
||||
* @see Import
|
||||
* @see ImportBeanDefinitionRegistrar
|
||||
* @see Configuration
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* Tests for {@link ImportSelector} and {@link DeferredImportSelector}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ImportSelectorTests {
|
||||
|
||||
@Test
|
||||
public void importSelectors() {
|
||||
DefaultListableBeanFactory beanFactory = spy(new DefaultListableBeanFactory());
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
beanFactory);
|
||||
context.register(Config.class);
|
||||
context.refresh();
|
||||
context.getBean(Config.class);
|
||||
InOrder ordered = inOrder(beanFactory);
|
||||
ordered.verify(beanFactory).registerBeanDefinition(eq("a"), any(BeanDefinition.class));
|
||||
ordered.verify(beanFactory).registerBeanDefinition(eq("b"), any(BeanDefinition.class));
|
||||
ordered.verify(beanFactory).registerBeanDefinition(eq("d"), any(BeanDefinition.class));
|
||||
ordered.verify(beanFactory).registerBeanDefinition(eq("c"), any(BeanDefinition.class));
|
||||
}
|
||||
|
||||
@Sample
|
||||
@Configuration
|
||||
static class Config {
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import({ DeferredImportSelector1.class, DeferredImportSelector2.class,
|
||||
ImportSelector1.class, ImportSelector2.class })
|
||||
public static @interface Sample {
|
||||
}
|
||||
|
||||
public static class ImportSelector1 implements ImportSelector {
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
|
||||
return new String[] { ImportedSelector1.class.getName() };
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImportSelector2 implements ImportSelector {
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
|
||||
return new String[] { ImportedSelector2.class.getName() };
|
||||
}
|
||||
}
|
||||
|
||||
public static class DeferredImportSelector1 implements DeferredImportSelector, Ordered {
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
|
||||
return new String[] { DeferredImportedSelector1.class.getName() };
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public static class DeferredImportSelector2 implements DeferredImportSelector {
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
|
||||
return new String[] { DeferredImportedSelector2.class.getName() };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class ImportedSelector1 {
|
||||
|
||||
@Bean
|
||||
public String a() {
|
||||
return "a";
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class ImportedSelector2 {
|
||||
|
||||
@Bean
|
||||
public String b() {
|
||||
return "b";
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class DeferredImportedSelector1 {
|
||||
|
||||
@Bean
|
||||
public String c() {
|
||||
return "c";
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class DeferredImportedSelector2 {
|
||||
|
||||
@Bean
|
||||
public String d() {
|
||||
return "d";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user