From 6921fdacaca3fa882b155daf071b01f73d37d640 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 26 Nov 2019 18:16:15 +0100 Subject: [PATCH] Expand configuration class eager filtering to imports Previously, only root auto-configuration classes could be excluded eagerly via an AutoConfigurationImportFilter. Any configuration class loaded as a result of processing a particular auto-configuration were parsed and checked as usual. This commit makes use of the `getExclusionFilter` callback to expand this filter to all candidates that are considered. The annotation processor has also be expanded to generate metadata for non-root configuration classes. Closes gh-12157 --- .../AutoConfigurationImportSelector.java | 110 +++++++++++------- .../AutoConfigurationImportSelectorTests.java | 10 ++ .../AutoConfigureAnnotationProcessor.java | 6 +- ...AutoConfigureAnnotationProcessorTests.java | 4 +- 4 files changed, 83 insertions(+), 47 deletions(-) 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 e26fab429c2..a51ae628938 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 @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.logging.Log; @@ -88,27 +89,33 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector, private ResourceLoader resourceLoader; + private ConfigurationClassFilter configurationClassFilter; + @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } - AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader - .loadMetadata(this.beanClassLoader); - AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, - annotationMetadata); + AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } + @Override + public Predicate getExclusionFilter() { + return this::shouldExclude; + } + + private boolean shouldExclude(String configurationClassName) { + return getConfigurationClassFilter().filter(Collections.singletonList(configurationClassName)).isEmpty(); + } + /** * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata} * of the importing {@link Configuration @Configuration} class. - * @param autoConfigurationMetadata the auto-configuration metadata * @param annotationMetadata the annotation metadata of the configuration class * @return the auto-configurations that should be imported */ - protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, - AnnotationMetadata annotationMetadata) { + protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } @@ -118,7 +125,7 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector, Set exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); - configurations = filter(configurations, autoConfigurationMetadata); + configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } @@ -236,41 +243,21 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector, return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList(); } - private List filter(List configurations, AutoConfigurationMetadata autoConfigurationMetadata) { - long startTime = System.nanoTime(); - String[] candidates = StringUtils.toStringArray(configurations); - boolean skipped = false; - for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { - invokeAwareMethods(filter); - boolean[] match = filter.match(candidates, autoConfigurationMetadata); - for (int i = 0; i < match.length; i++) { - if (!match[i]) { - candidates[i] = null; - skipped = true; - } - } - } - if (!skipped) { - return configurations; - } - List result = new ArrayList<>(candidates.length); - for (String candidate : candidates) { - if (candidate != null) { - result.add(candidate); - } - } - if (logger.isTraceEnabled()) { - int numberFiltered = configurations.size() - result.size(); - logger.trace("Filtered " + numberFiltered + " auto configuration class in " - + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); - } - return result; - } - protected List getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); } + private ConfigurationClassFilter getConfigurationClassFilter() { + if (this.configurationClassFilter == null) { + List filters = getAutoConfigurationImportFilters(); + for (AutoConfigurationImportFilter filter : filters) { + invokeAwareMethods(filter); + } + this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters); + } + return this.configurationClassFilter; + } + protected final List removeDuplicates(List list) { return new ArrayList<>(new LinkedHashSet<>(list)); } @@ -354,6 +341,49 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector, return Ordered.LOWEST_PRECEDENCE - 1; } + private static class ConfigurationClassFilter { + + private final AutoConfigurationMetadata autoConfigurationMetadata; + + private final List filters; + + ConfigurationClassFilter(ClassLoader classLoader, List filters) { + this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader); + this.filters = filters; + } + + List filter(List configurations) { + long startTime = System.nanoTime(); + String[] candidates = StringUtils.toStringArray(configurations); + boolean skipped = false; + for (AutoConfigurationImportFilter filter : this.filters) { + boolean[] match = filter.match(candidates, this.autoConfigurationMetadata); + for (int i = 0; i < match.length; i++) { + if (!match[i]) { + candidates[i] = null; + skipped = true; + } + } + } + if (!skipped) { + return configurations; + } + List result = new ArrayList<>(candidates.length); + for (String candidate : candidates) { + if (candidate != null) { + result.add(candidate); + } + } + if (logger.isTraceEnabled()) { + int numberFiltered = configurations.size() - result.size(); + logger.trace("Filtered " + numberFiltered + " auto configuration class in " + + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); + } + return result; + } + + } + private static class AutoConfigurationGroup implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware { @@ -391,7 +421,7 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector, AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) - .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); + .getAutoConfigurationEntry(annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java index e14fc550863..7a1539f8df8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java @@ -200,6 +200,16 @@ class AutoConfigurationImportSelectorTests { assertThat(filter.getBeanFactory()).isEqualTo(this.beanFactory); } + @Test + void getExclusionFilterReuseFilters() { + String[] allImports = new String[] { "com.example.A", "com.example.B", "com.example.C" }; + this.filters.add(new TestAutoConfigurationImportFilter(allImports, 0)); + this.filters.add(new TestAutoConfigurationImportFilter(allImports, 2)); + assertThat(this.importSelector.getExclusionFilter().test("com.example.A")).isTrue(); + assertThat(this.importSelector.getExclusionFilter().test("com.example.B")).isFalse(); + assertThat(this.importSelector.getExclusionFilter().test("com.example.C")).isTrue(); + } + private String[] selectImports(Class source) { return this.importSelector.selectImports(AnnotationMetadata.introspect(source)); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java index 0a1500a8f11..7eba7714f68 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java @@ -40,7 +40,6 @@ import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.tools.FileObject; @@ -127,10 +126,7 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor { TypeElement annotationType = this.processingEnv.getElementUtils().getTypeElement(annotationName); if (annotationType != null) { for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) { - Element enclosingElement = element.getEnclosingElement(); - if (enclosingElement != null && enclosingElement.getKind() == ElementKind.PACKAGE) { - processElement(element, propertyKey, annotationName); - } + processElement(element, propertyKey, annotationName); } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessorTests.java index 49873ec4fea..62fe8017182 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessorTests.java @@ -49,14 +49,14 @@ class AutoConfigureAnnotationProcessorTests { @Test void annotatedClass() throws Exception { Properties properties = compile(TestClassConfiguration.class); - assertThat(properties).hasSize(5); + assertThat(properties).hasSize(7); assertThat(properties).containsEntry( "org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.ConditionalOnClass", "java.io.InputStream,org.springframework.boot.autoconfigureprocessor." + "TestClassConfiguration$Nested,org.springframework.foo"); assertThat(properties).containsKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration"); assertThat(properties) - .doesNotContainKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration$Nested"); + .containsKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration$Nested"); assertThat(properties).containsEntry( "org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.ConditionalOnBean", "java.io.OutputStream");