From 26d9c261c5bedc56c7a2eba5d539c142b5cc2eab Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 14 Mar 2018 05:01:30 -0400 Subject: [PATCH] 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 --- .../AutoConfigurationImportSelector.java | 124 +++++++++++----- ...urationImportSelectorIntegrationTests.java | 132 ++++++++++++++++++ 2 files changed, 219 insertions(+), 37 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorIntegrationTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java index 4b2c457f873..b778e803d48 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java @@ -16,14 +16,16 @@ package org.springframework.boot.autoconfigure; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -88,24 +90,23 @@ public class AutoConfigurationImportSelector if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } - try { - AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader - .loadMetadata(this.beanClassLoader); - AnnotationAttributes attributes = getAttributes(annotationMetadata); - List configurations = getCandidateConfigurations(annotationMetadata, - attributes); - configurations = removeDuplicates(configurations); - configurations = sort(configurations, autoConfigurationMetadata); - Set exclusions = getExclusions(annotationMetadata, attributes); - checkExcludedClasses(configurations, exclusions); - configurations.removeAll(exclusions); - configurations = filter(configurations, autoConfigurationMetadata); - fireAutoConfigurationImportEvents(configurations, exclusions); - return StringUtils.toStringArray(configurations); - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } + AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader + .loadMetadata(this.beanClassLoader); + AnnotationAttributes attributes = getAttributes(annotationMetadata); + List configurations = getCandidateConfigurations(annotationMetadata, + attributes); + configurations = removeDuplicates(configurations); + Set exclusions = getExclusions(annotationMetadata, attributes); + checkExcludedClasses(configurations, exclusions); + configurations.removeAll(exclusions); + configurations = filter(configurations, autoConfigurationMetadata); + fireAutoConfigurationImportEvents(configurations, exclusions); + return StringUtils.toStringArray(configurations); + } + + @Override + public Class getImportGroup() { + return AutoConfigurationGroup.class; } protected boolean isEnabled(AnnotationMetadata metadata) { @@ -226,13 +227,6 @@ public class AutoConfigurationImportSelector return (excludes == null ? Collections.emptyList() : Arrays.asList(excludes)); } - private List sort(List configurations, - AutoConfigurationMetadata autoConfigurationMetadata) throws IOException { - configurations = new AutoConfigurationSorter(getMetadataReaderFactory(), - autoConfigurationMetadata).getInPriorityOrder(configurations); - return configurations; - } - private List filter(List configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); @@ -272,17 +266,6 @@ public class AutoConfigurationImportSelector this.beanClassLoader); } - private MetadataReaderFactory getMetadataReaderFactory() { - try { - return getBeanFactory().getBean( - SharedMetadataReaderFactoryContextInitializer.BEAN_NAME, - MetadataReaderFactory.class); - } - catch (NoSuchBeanDefinitionException ex) { - return new CachingMetadataReaderFactory(this.resourceLoader); - } - } - protected final List removeDuplicates(List list) { return new ArrayList<>(new LinkedHashSet<>(list)); } @@ -370,4 +353,71 @@ public class AutoConfigurationImportSelector 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 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 selectImports() { + return sortAutoConfigurations().stream().map((importClassName) -> + new Entry(this.entries.get(importClassName), importClassName)) + .collect(Collectors.toList()); + } + + private List sortAutoConfigurations() { + List 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); + } + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorIntegrationTests.java new file mode 100644 index 00000000000..0849fdcd152 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorIntegrationTests.java @@ -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 getImportedConfigBeans(AssertableApplicationContext context) { + String shortName = ClassUtils.getShortName( + AutoConfigurationImportSelectorIntegrationTests.class); + int beginIndex = shortName.length() + 1; + List 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 { + + } + +}