Group auto-configuration import selectors together

This commit updates Spring Boot's DeferredImportSelector implementations
to group imports in a consistent set. This makes sure ordering is
applied consistently.

Closes gh-12366
pull/12480/head
Stephane Nicoll 7 years ago
parent 3ae4a541b6
commit 26d9c261c5

@ -16,14 +16,16 @@
package org.springframework.boot.autoconfigure; package org.springframework.boot.autoconfigure;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -88,24 +90,23 @@ public class AutoConfigurationImportSelector
if (!isEnabled(annotationMetadata)) { if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS; return NO_IMPORTS;
} }
try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader);
.loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata);
AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata,
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
attributes); configurations = removeDuplicates(configurations);
configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes);
configurations = sort(configurations, autoConfigurationMetadata); checkExcludedClasses(configurations, exclusions);
Set<String> exclusions = getExclusions(annotationMetadata, attributes); configurations.removeAll(exclusions);
checkExcludedClasses(configurations, exclusions); configurations = filter(configurations, autoConfigurationMetadata);
configurations.removeAll(exclusions); fireAutoConfigurationImportEvents(configurations, exclusions);
configurations = filter(configurations, autoConfigurationMetadata); return StringUtils.toStringArray(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions); }
return StringUtils.toStringArray(configurations);
} @Override
catch (IOException ex) { public Class<? extends Group> getImportGroup() {
throw new IllegalStateException(ex); return AutoConfigurationGroup.class;
}
} }
protected boolean isEnabled(AnnotationMetadata metadata) { protected boolean isEnabled(AnnotationMetadata metadata) {
@ -226,13 +227,6 @@ public class AutoConfigurationImportSelector
return (excludes == null ? Collections.emptyList() : Arrays.asList(excludes)); return (excludes == null ? Collections.emptyList() : Arrays.asList(excludes));
} }
private List<String> sort(List<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) throws IOException {
configurations = new AutoConfigurationSorter(getMetadataReaderFactory(),
autoConfigurationMetadata).getInPriorityOrder(configurations);
return configurations;
}
private List<String> filter(List<String> configurations, private List<String> filter(List<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) { AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime(); long startTime = System.nanoTime();
@ -272,17 +266,6 @@ public class AutoConfigurationImportSelector
this.beanClassLoader); this.beanClassLoader);
} }
private MetadataReaderFactory getMetadataReaderFactory() {
try {
return getBeanFactory().getBean(
SharedMetadataReaderFactoryContextInitializer.BEAN_NAME,
MetadataReaderFactory.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new CachingMetadataReaderFactory(this.resourceLoader);
}
}
protected final <T> List<T> removeDuplicates(List<T> list) { protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list)); return new ArrayList<>(new LinkedHashSet<>(list));
} }
@ -370,4 +353,71 @@ public class AutoConfigurationImportSelector
return Ordered.LOWEST_PRECEDENCE - 1; return Ordered.LOWEST_PRECEDENCE - 1;
} }
private static class AutoConfigurationGroup implements DeferredImportSelector.Group,
BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
private ClassLoader beanClassLoader;
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void process(AnnotationMetadata annotationMetadata,
DeferredImportSelector deferredImportSelector) {
String[] imports = deferredImportSelector.selectImports(annotationMetadata);
for (String importClassName : imports) {
this.entries.put(importClassName, annotationMetadata);
}
}
@Override
public Iterable<Entry> selectImports() {
return sortAutoConfigurations().stream().map((importClassName) ->
new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
private List<String> sortAutoConfigurations() {
List<String> autoConfigurations = new ArrayList<>(this.entries.keySet());
if (this.entries.size() <= 1) {
return autoConfigurations;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
return new AutoConfigurationSorter(getMetadataReaderFactory(),
autoConfigurationMetadata).getInPriorityOrder(autoConfigurations);
}
private MetadataReaderFactory getMetadataReaderFactory() {
try {
return this.beanFactory.getBean(
SharedMetadataReaderFactoryContextInitializer.BEAN_NAME,
MetadataReaderFactory.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new CachingMetadataReaderFactory(this.resourceLoader);
}
}
}
} }

@ -0,0 +1,132 @@
/*
* Copyright 2012-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.
* 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.boot.autoconfigure;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link AutoConfigurationImportSelector}.
*
* @author Stephane Nicoll
*/
public class AutoConfigurationImportSelectorIntegrationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
@Test
public void singleSelectorWithNoImports() {
this.contextRunner.withUserConfiguration(NoConfig.class)
.run((context) ->
assertThat(getImportedConfigBeans(context)).isEmpty());
}
@Test
public void singleSelector() {
this.contextRunner.withUserConfiguration(SingleConfig.class)
.run((context) -> {
assertThat(getImportedConfigBeans(context)).containsExactly(
"ConfigC");
});
}
@Test
public void multipleSelectorShouldMergeAndSortCorrectly() {
this.contextRunner.withUserConfiguration(Config.class, AnotherConfig.class)
.run((context) -> {
assertThat(getImportedConfigBeans(context)).containsExactly(
"ConfigA", "ConfigB", "ConfigC", "ConfigD");
});
}
@Test
public void multipleSelectorWithRedundantImportsShouldMergeAndSortCorrectly() {
this.contextRunner.withUserConfiguration(SingleConfig.class, Config.class,
AnotherConfig.class).run((context) -> {
assertThat(getImportedConfigBeans(context)).containsExactly(
"ConfigA", "ConfigB", "ConfigC", "ConfigD");
});
}
private List<String> getImportedConfigBeans(AssertableApplicationContext context) {
String shortName = ClassUtils.getShortName(
AutoConfigurationImportSelectorIntegrationTests.class);
int beginIndex = shortName.length() + 1;
List<String> orderedConfigBeans = new ArrayList<>();
for (String bean : context.getBeanDefinitionNames()) {
if (bean.contains("$Config")) {
String shortBeanName = ClassUtils.getShortName(bean);
orderedConfigBeans.add(shortBeanName.substring(beginIndex));
}
}
return orderedConfigBeans;
}
@ImportAutoConfiguration
static class NoConfig {
}
@ImportAutoConfiguration(ConfigC.class)
static class SingleConfig {
}
@ImportAutoConfiguration({ ConfigD.class, ConfigB.class })
static class Config {
}
@ImportAutoConfiguration({ ConfigC.class, ConfigA.class })
static class AnotherConfig {
}
@Configuration
static class ConfigA {
}
@Configuration
@AutoConfigureAfter(ConfigA.class)
@AutoConfigureBefore(ConfigC.class)
static class ConfigB {
}
@Configuration
static class ConfigC {
}
@Configuration
@AutoConfigureAfter(ConfigC.class)
static class ConfigD {
}
}
Loading…
Cancel
Save