Add support for deferred import selector group
This commit allows several DeferredImportSelector instances to be grouped and managed in a centralized fashion. This typically allows different instances to provide a consistent ordered set of imports to apply. Issue: SPR-16589
This commit is contained in:
@@ -51,6 +51,7 @@ 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.annotation.ConfigurationCondition.ConfigurationPhase;
|
||||
import org.springframework.context.annotation.DeferredImportSelector.Group;
|
||||
import org.springframework.core.NestedIOException;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.Ordered;
|
||||
@@ -100,6 +101,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @author Stephane Nicoll
|
||||
* @since 3.0
|
||||
* @see ConfigurationClassBeanDefinitionReader
|
||||
*/
|
||||
@@ -543,21 +545,46 @@ class ConfigurationClassParser {
|
||||
}
|
||||
|
||||
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
|
||||
Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
|
||||
Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
|
||||
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
|
||||
ConfigurationClass configClass = deferredImport.getConfigurationClass();
|
||||
try {
|
||||
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
|
||||
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
|
||||
}
|
||||
catch (BeanDefinitionStoreException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanDefinitionStoreException(
|
||||
"Failed to process import candidates for configuration class [" +
|
||||
configClass.getMetadata().getClassName() + "]", ex);
|
||||
}
|
||||
Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
|
||||
DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent(
|
||||
(group == null ? deferredImport : group),
|
||||
(key) -> new DeferredImportSelectorGrouping(createGroup(group)));
|
||||
grouping.add(deferredImport);
|
||||
configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
|
||||
deferredImport.getConfigurationClass());
|
||||
}
|
||||
for (DeferredImportSelectorGrouping grouping : groupings.values()) {
|
||||
grouping.getImports().forEach((entry) -> {
|
||||
ConfigurationClass configurationClass = configurationClasses.get(
|
||||
entry.getMetadata());
|
||||
try {
|
||||
processImports(configurationClass, asSourceClass(configurationClass),
|
||||
asSourceClasses(entry.getImportClassName()), false);
|
||||
}
|
||||
catch (BeanDefinitionStoreException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanDefinitionStoreException(
|
||||
"Failed to process import candidates for configuration class [" +
|
||||
configurationClass.getMetadata().getClassName() + "]", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Group createGroup(@Nullable Class<? extends Group> type) {
|
||||
Class<? extends Group> effectiveType = (type != null ? type
|
||||
: DefaultDeferredImportSelectorGroup.class);
|
||||
Group group = BeanUtils.instantiateClass(effectiveType);
|
||||
ParserStrategyUtils.invokeAwareMethods(group,
|
||||
ConfigurationClassParser.this.environment,
|
||||
ConfigurationClassParser.this.resourceLoader,
|
||||
ConfigurationClassParser.this.registry);
|
||||
return group;
|
||||
}
|
||||
|
||||
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
|
||||
@@ -677,7 +704,7 @@ class ConfigurationClassParser {
|
||||
/**
|
||||
* Factory method to obtain {@link SourceClass}s from class names.
|
||||
*/
|
||||
private Collection<SourceClass> asSourceClasses(String[] classNames) throws IOException {
|
||||
private Collection<SourceClass> asSourceClasses(String... classNames) throws IOException {
|
||||
List<SourceClass> annotatedClasses = new ArrayList<>(classNames.length);
|
||||
for (String className : classNames) {
|
||||
annotatedClasses.add(asSourceClass(className));
|
||||
@@ -777,6 +804,52 @@ class ConfigurationClassParser {
|
||||
}
|
||||
|
||||
|
||||
private static class DeferredImportSelectorGrouping {
|
||||
|
||||
private final DeferredImportSelector.Group group;
|
||||
|
||||
private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();
|
||||
|
||||
DeferredImportSelectorGrouping(Group group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public void add(DeferredImportSelectorHolder deferredImport) {
|
||||
this.deferredImports.add(deferredImport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the imports defined by the group.
|
||||
* @return each import with its associated configuration class
|
||||
*/
|
||||
public Iterable<Group.Entry> getImports() {
|
||||
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
|
||||
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
|
||||
deferredImport.getImportSelector());
|
||||
}
|
||||
return this.group.selectImports();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class DefaultDeferredImportSelectorGroup implements Group {
|
||||
|
||||
private final List<Entry> imports = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
|
||||
for (String importClassName : selector.selectImports(metadata)) {
|
||||
this.imports.add(new Entry(metadata, importClassName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Entry> selectImports() {
|
||||
return this.imports;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple wrapper that allows annotated source classes to be dealt with
|
||||
* in a uniform manner, regardless of how they are loaded.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2018 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,6 +16,11 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -25,9 +30,90 @@ package org.springframework.context.annotation;
|
||||
* interface or use the {@link org.springframework.core.annotation.Order} annotation to
|
||||
* indicate a precedence against other {@link DeferredImportSelector}s.
|
||||
*
|
||||
* <p>Implementations may also provide an {@link #getImportGroup() import group} which
|
||||
* can provide additional sorting and filtering logic across different selectors.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface DeferredImportSelector extends ImportSelector {
|
||||
|
||||
/**
|
||||
* Return a specific import group or {@code null} if no grouping is required.
|
||||
* @return the import group class or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
default Class<? extends Group> getImportGroup() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface used to group results from different import selectors.
|
||||
*/
|
||||
interface Group {
|
||||
|
||||
/**
|
||||
* Process the {@link AnnotationMetadata} of the importing @{@link Configuration}
|
||||
* class using the specified {@link DeferredImportSelector}.
|
||||
*/
|
||||
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
|
||||
|
||||
/**
|
||||
* Return the {@link Entry entries} of which class(es) should be imported for this
|
||||
* group.
|
||||
*/
|
||||
Iterable<Entry> selectImports();
|
||||
|
||||
/**
|
||||
* An entry that holds the {@link AnnotationMetadata} of the importing
|
||||
* {@link Configuration} class and the class name to import.
|
||||
*/
|
||||
class Entry {
|
||||
|
||||
private final AnnotationMetadata metadata;
|
||||
|
||||
private final String importClassName;
|
||||
|
||||
public Entry(AnnotationMetadata metadata, String importClassName) {
|
||||
this.metadata = metadata;
|
||||
this.importClassName = importClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link AnnotationMetadata} of the importing
|
||||
* {@link Configuration} class.
|
||||
*/
|
||||
public AnnotationMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully qualified name of the class to import.
|
||||
*/
|
||||
public String getImportClassName() {
|
||||
return this.importClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Entry entry = (Entry) o;
|
||||
return Objects.equals(this.metadata, entry.metadata) &&
|
||||
Objects.equals(this.importClassName, entry.importClassName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.metadata, this.importClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user