Skip to content

Commit 6921fda

Browse files
committed
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
1 parent 0cbc5a7 commit 6921fda

File tree

4 files changed

+83
-47
lines changed

4 files changed

+83
-47
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Map;
2828
import java.util.Set;
2929
import java.util.concurrent.TimeUnit;
30+
import java.util.function.Predicate;
3031
import java.util.stream.Collectors;
3132

3233
import org.apache.commons.logging.Log;
@@ -88,27 +89,33 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
8889

8990
private ResourceLoader resourceLoader;
9091

92+
private ConfigurationClassFilter configurationClassFilter;
93+
9194
@Override
9295
public String[] selectImports(AnnotationMetadata annotationMetadata) {
9396
if (!isEnabled(annotationMetadata)) {
9497
return NO_IMPORTS;
9598
}
96-
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
97-
.loadMetadata(this.beanClassLoader);
98-
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
99-
annotationMetadata);
99+
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
100100
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
101101
}
102102

103+
@Override
104+
public Predicate<String> getExclusionFilter() {
105+
return this::shouldExclude;
106+
}
107+
108+
private boolean shouldExclude(String configurationClassName) {
109+
return getConfigurationClassFilter().filter(Collections.singletonList(configurationClassName)).isEmpty();
110+
}
111+
103112
/**
104113
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
105114
* of the importing {@link Configuration @Configuration} class.
106-
* @param autoConfigurationMetadata the auto-configuration metadata
107115
* @param annotationMetadata the annotation metadata of the configuration class
108116
* @return the auto-configurations that should be imported
109117
*/
110-
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
111-
AnnotationMetadata annotationMetadata) {
118+
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
112119
if (!isEnabled(annotationMetadata)) {
113120
return EMPTY_ENTRY;
114121
}
@@ -118,7 +125,7 @@ protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMeta
118125
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
119126
checkExcludedClasses(configurations, exclusions);
120127
configurations.removeAll(exclusions);
121-
configurations = filter(configurations, autoConfigurationMetadata);
128+
configurations = getConfigurationClassFilter().filter(configurations);
122129
fireAutoConfigurationImportEvents(configurations, exclusions);
123130
return new AutoConfigurationEntry(configurations, exclusions);
124131
}
@@ -236,41 +243,21 @@ private List<String> getExcludeAutoConfigurationsProperty() {
236243
return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
237244
}
238245

239-
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
240-
long startTime = System.nanoTime();
241-
String[] candidates = StringUtils.toStringArray(configurations);
242-
boolean skipped = false;
243-
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
244-
invokeAwareMethods(filter);
245-
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
246-
for (int i = 0; i < match.length; i++) {
247-
if (!match[i]) {
248-
candidates[i] = null;
249-
skipped = true;
250-
}
251-
}
252-
}
253-
if (!skipped) {
254-
return configurations;
255-
}
256-
List<String> result = new ArrayList<>(candidates.length);
257-
for (String candidate : candidates) {
258-
if (candidate != null) {
259-
result.add(candidate);
260-
}
261-
}
262-
if (logger.isTraceEnabled()) {
263-
int numberFiltered = configurations.size() - result.size();
264-
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
265-
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
266-
}
267-
return result;
268-
}
269-
270246
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
271247
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
272248
}
273249

250+
private ConfigurationClassFilter getConfigurationClassFilter() {
251+
if (this.configurationClassFilter == null) {
252+
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
253+
for (AutoConfigurationImportFilter filter : filters) {
254+
invokeAwareMethods(filter);
255+
}
256+
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
257+
}
258+
return this.configurationClassFilter;
259+
}
260+
274261
protected final <T> List<T> removeDuplicates(List<T> list) {
275262
return new ArrayList<>(new LinkedHashSet<>(list));
276263
}
@@ -354,6 +341,49 @@ public int getOrder() {
354341
return Ordered.LOWEST_PRECEDENCE - 1;
355342
}
356343

344+
private static class ConfigurationClassFilter {
345+
346+
private final AutoConfigurationMetadata autoConfigurationMetadata;
347+
348+
private final List<AutoConfigurationImportFilter> filters;
349+
350+
ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
351+
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
352+
this.filters = filters;
353+
}
354+
355+
List<String> filter(List<String> configurations) {
356+
long startTime = System.nanoTime();
357+
String[] candidates = StringUtils.toStringArray(configurations);
358+
boolean skipped = false;
359+
for (AutoConfigurationImportFilter filter : this.filters) {
360+
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
361+
for (int i = 0; i < match.length; i++) {
362+
if (!match[i]) {
363+
candidates[i] = null;
364+
skipped = true;
365+
}
366+
}
367+
}
368+
if (!skipped) {
369+
return configurations;
370+
}
371+
List<String> result = new ArrayList<>(candidates.length);
372+
for (String candidate : candidates) {
373+
if (candidate != null) {
374+
result.add(candidate);
375+
}
376+
}
377+
if (logger.isTraceEnabled()) {
378+
int numberFiltered = configurations.size() - result.size();
379+
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
380+
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
381+
}
382+
return result;
383+
}
384+
385+
}
386+
357387
private static class AutoConfigurationGroup
358388
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
359389

@@ -391,7 +421,7 @@ public void process(AnnotationMetadata annotationMetadata, DeferredImportSelecto
391421
AutoConfigurationImportSelector.class.getSimpleName(),
392422
deferredImportSelector.getClass().getName()));
393423
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
394-
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
424+
.getAutoConfigurationEntry(annotationMetadata);
395425
this.autoConfigurationEntries.add(autoConfigurationEntry);
396426
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
397427
this.entries.putIfAbsent(importClassName, annotationMetadata);

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,16 @@ void filterShouldSupportAware() {
200200
assertThat(filter.getBeanFactory()).isEqualTo(this.beanFactory);
201201
}
202202

203+
@Test
204+
void getExclusionFilterReuseFilters() {
205+
String[] allImports = new String[] { "com.example.A", "com.example.B", "com.example.C" };
206+
this.filters.add(new TestAutoConfigurationImportFilter(allImports, 0));
207+
this.filters.add(new TestAutoConfigurationImportFilter(allImports, 2));
208+
assertThat(this.importSelector.getExclusionFilter().test("com.example.A")).isTrue();
209+
assertThat(this.importSelector.getExclusionFilter().test("com.example.B")).isFalse();
210+
assertThat(this.importSelector.getExclusionFilter().test("com.example.C")).isTrue();
211+
}
212+
203213
private String[] selectImports(Class<?> source) {
204214
return this.importSelector.selectImports(AnnotationMetadata.introspect(source));
205215
}

spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import javax.lang.model.element.AnnotationMirror;
4141
import javax.lang.model.element.AnnotationValue;
4242
import javax.lang.model.element.Element;
43-
import javax.lang.model.element.ElementKind;
4443
import javax.lang.model.element.TypeElement;
4544
import javax.lang.model.type.DeclaredType;
4645
import javax.tools.FileObject;
@@ -127,10 +126,7 @@ private void process(RoundEnvironment roundEnv, String propertyKey, String annot
127126
TypeElement annotationType = this.processingEnv.getElementUtils().getTypeElement(annotationName);
128127
if (annotationType != null) {
129128
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
130-
Element enclosingElement = element.getEnclosingElement();
131-
if (enclosingElement != null && enclosingElement.getKind() == ElementKind.PACKAGE) {
132-
processElement(element, propertyKey, annotationName);
133-
}
129+
processElement(element, propertyKey, annotationName);
134130
}
135131
}
136132
}

spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessorTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ void createCompiler() throws IOException {
4949
@Test
5050
void annotatedClass() throws Exception {
5151
Properties properties = compile(TestClassConfiguration.class);
52-
assertThat(properties).hasSize(5);
52+
assertThat(properties).hasSize(7);
5353
assertThat(properties).containsEntry(
5454
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.ConditionalOnClass",
5555
"java.io.InputStream,org.springframework.boot.autoconfigureprocessor."
5656
+ "TestClassConfiguration$Nested,org.springframework.foo");
5757
assertThat(properties).containsKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration");
5858
assertThat(properties)
59-
.doesNotContainKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration$Nested");
59+
.containsKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration$Nested");
6060
assertThat(properties).containsEntry(
6161
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.ConditionalOnBean",
6262
"java.io.OutputStream");

0 commit comments

Comments
 (0)