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:
Phillip Webb
2013-02-21 16:47:00 -08:00
parent b257253a2b
commit 7c7fdb0736
5 changed files with 262 additions and 16 deletions

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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 {
}

View File

@@ -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