Order class filter checks and exit early
Update the logic in `OnClassCondition` so that filtering exits on the first missing class. Also refactor the implementation to save unnecessary `Set` creation when there is just a single class to check. The `AutoConfigureAnnotationProcessor` has also been updated to order classes so that any starting `org.springframework` are considered last. The assumption being that other classes are more likely to be missing. Closes gh-12131
This commit is contained in:
parent
b1d4cf4ea8
commit
85f86243c9
|
@ -20,7 +20,6 @@ import java.security.AccessControlException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
|
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
|
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
|
||||||
|
@ -31,6 +30,7 @@ import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Condition} and {@link AutoConfigurationImportFilter} that checks for the
|
* {@link Condition} and {@link AutoConfigurationImportFilter} that checks for the
|
||||||
|
@ -200,8 +200,8 @@ class OnClassCondition extends FilteringSpringBootCondition {
|
||||||
for (int i = start; i < end; i++) {
|
for (int i = start; i < end; i++) {
|
||||||
String autoConfigurationClass = autoConfigurationClasses[i];
|
String autoConfigurationClass = autoConfigurationClasses[i];
|
||||||
if (autoConfigurationClass != null) {
|
if (autoConfigurationClass != null) {
|
||||||
Set<String> candidates = autoConfigurationMetadata
|
String candidates = autoConfigurationMetadata
|
||||||
.getSet(autoConfigurationClass, "ConditionalOnClass");
|
.get(autoConfigurationClass, "ConditionalOnClass");
|
||||||
if (candidates != null) {
|
if (candidates != null) {
|
||||||
outcomes[i - start] = getOutcome(candidates);
|
outcomes[i - start] = getOutcome(candidates);
|
||||||
}
|
}
|
||||||
|
@ -210,15 +210,19 @@ class OnClassCondition extends FilteringSpringBootCondition {
|
||||||
return outcomes;
|
return outcomes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConditionOutcome getOutcome(Set<String> candidates) {
|
private ConditionOutcome getOutcome(String candidates) {
|
||||||
try {
|
try {
|
||||||
List<String> missing = filter(candidates, ClassNameFilter.MISSING,
|
if (!candidates.contains(",")) {
|
||||||
this.beanClassLoader);
|
return getOutcome(candidates, ClassNameFilter.MISSING,
|
||||||
if (!missing.isEmpty()) {
|
this.beanClassLoader);
|
||||||
return ConditionOutcome.noMatch(
|
}
|
||||||
ConditionMessage.forCondition(ConditionalOnClass.class)
|
for (String candidate : StringUtils
|
||||||
.didNotFind("required class", "required classes")
|
.commaDelimitedListToStringArray(candidates)) {
|
||||||
.items(Style.QUOTE, missing));
|
ConditionOutcome outcome = getOutcome(candidate,
|
||||||
|
ClassNameFilter.MISSING, this.beanClassLoader);
|
||||||
|
if (outcome != null) {
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
|
@ -227,6 +231,16 @@ class OnClassCondition extends FilteringSpringBootCondition {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConditionOutcome getOutcome(String className,
|
||||||
|
ClassNameFilter classNameFilter, ClassLoader classLoader) {
|
||||||
|
if (classNameFilter.matches(className, classLoader)) {
|
||||||
|
return ConditionOutcome.noMatch(ConditionMessage
|
||||||
|
.forCondition(ConditionalOnClass.class)
|
||||||
|
.didNotFind("required class").items(Style.QUOTE, className));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2017 the original author or authors.
|
* Copyright 2012-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.boot.autoconfigure.condition;
|
package org.springframework.boot.autoconfigure.condition;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -75,11 +73,11 @@ public class OnClassConditionAutoConfigurationImportFilterTests {
|
||||||
private AutoConfigurationMetadata getAutoConfigurationMetadata() {
|
private AutoConfigurationMetadata getAutoConfigurationMetadata() {
|
||||||
AutoConfigurationMetadata metadata = mock(AutoConfigurationMetadata.class);
|
AutoConfigurationMetadata metadata = mock(AutoConfigurationMetadata.class);
|
||||||
given(metadata.wasProcessed("test.match")).willReturn(true);
|
given(metadata.wasProcessed("test.match")).willReturn(true);
|
||||||
given(metadata.getSet("test.match", "ConditionalOnClass"))
|
given(metadata.get("test.match", "ConditionalOnClass"))
|
||||||
.willReturn(Collections.singleton("java.io.InputStream"));
|
.willReturn("java.io.InputStream");
|
||||||
given(metadata.wasProcessed("test.nomatch")).willReturn(true);
|
given(metadata.wasProcessed("test.nomatch")).willReturn(true);
|
||||||
given(metadata.getSet("test.nomatch", "ConditionalOnClass"))
|
given(metadata.get("test.nomatch", "ConditionalOnClass"))
|
||||||
.willReturn(Collections.singleton("java.io.DoesNotExist"));
|
.willReturn("java.io.DoesNotExist");
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.io.OutputStream;
|
||||||
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.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -98,7 +99,7 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
private void addValueExtractors(Map<String, ValueExtractor> attributes) {
|
private void addValueExtractors(Map<String, ValueExtractor> attributes) {
|
||||||
attributes.put("Configuration", ValueExtractor.allFrom("value"));
|
attributes.put("Configuration", ValueExtractor.allFrom("value"));
|
||||||
attributes.put("ConditionalOnClass", ValueExtractor.allFrom("value", "name"));
|
attributes.put("ConditionalOnClass", new OnClassConditionValueExtractor());
|
||||||
attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor());
|
attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor());
|
||||||
attributes.put("ConditionalOnSingleCandidate",
|
attributes.put("ConditionalOnSingleCandidate",
|
||||||
new OnBeanConditionValueExtractor());
|
new OnBeanConditionValueExtractor());
|
||||||
|
@ -206,22 +207,8 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
List<Object> getValues(AnnotationMirror annotation);
|
List<Object> getValues(AnnotationMirror annotation);
|
||||||
|
|
||||||
static ValueExtractor allFrom(String... attributes) {
|
static ValueExtractor allFrom(String... names) {
|
||||||
Set<String> names = new HashSet<>(Arrays.asList(attributes));
|
return new NamedValuesExtractor(names);
|
||||||
return new AbstractValueExtractor() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Object> getValues(AnnotationMirror annotation) {
|
|
||||||
List<Object> result = new ArrayList<>();
|
|
||||||
annotation.getElementValues().forEach((key, value) -> {
|
|
||||||
if (names.contains(key.getSimpleName().toString())) {
|
|
||||||
extractValues(value).forEach(result::add);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -250,6 +237,27 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class NamedValuesExtractor extends AbstractValueExtractor {
|
||||||
|
|
||||||
|
private final Set<String> names;
|
||||||
|
|
||||||
|
NamedValuesExtractor(String... names) {
|
||||||
|
this.names = new HashSet<>(Arrays.asList(names));
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Object> getValues(AnnotationMirror annotation) {
|
||||||
|
List<Object> result = new ArrayList<>();
|
||||||
|
annotation.getElementValues().forEach((key, value) -> {
|
||||||
|
if (this.names.contains(key.getSimpleName().toString())) {
|
||||||
|
extractValues(value).forEach(result::add);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static class OnBeanConditionValueExtractor extends AbstractValueExtractor {
|
private static class OnBeanConditionValueExtractor extends AbstractValueExtractor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -268,4 +276,29 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class OnClassConditionValueExtractor extends NamedValuesExtractor {
|
||||||
|
|
||||||
|
OnClassConditionValueExtractor() {
|
||||||
|
super("value", "name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Object> getValues(AnnotationMirror annotation) {
|
||||||
|
List<Object> values = super.getValues(annotation);
|
||||||
|
Collections.sort(values, this::compare);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int compare(Object o1, Object o2) {
|
||||||
|
return Comparator.comparing(this::isSpringClass)
|
||||||
|
.thenComparing(String.CASE_INSENSITIVE_ORDER)
|
||||||
|
.compare(o1.toString(), o2.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSpringClass(String type) {
|
||||||
|
return type.startsWith("org.springframework");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ public class AutoConfigureAnnotationProcessorTests {
|
||||||
"org.springframework.boot.autoconfigureprocessor."
|
"org.springframework.boot.autoconfigureprocessor."
|
||||||
+ "TestClassConfiguration.ConditionalOnClass",
|
+ "TestClassConfiguration.ConditionalOnClass",
|
||||||
"java.io.InputStream,org.springframework.boot.autoconfigureprocessor."
|
"java.io.InputStream,org.springframework.boot.autoconfigureprocessor."
|
||||||
+ "TestClassConfiguration$Nested");
|
+ "TestClassConfiguration$Nested,org.springframework.foo");
|
||||||
assertThat(properties)
|
assertThat(properties)
|
||||||
.containsKey("org.springframework.boot.autoconfigureprocessor."
|
.containsKey("org.springframework.boot.autoconfigureprocessor."
|
||||||
+ "TestClassConfiguration");
|
+ "TestClassConfiguration");
|
||||||
|
|
|
@ -24,7 +24,8 @@ import org.springframework.boot.autoconfigureprocessor.TestConditionalOnWebAppli
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
*/
|
*/
|
||||||
@TestConfiguration
|
@TestConfiguration
|
||||||
@TestConditionalOnClass(name = "java.io.InputStream", value = TestClassConfiguration.Nested.class)
|
@TestConditionalOnClass(name = { "org.springframework.foo",
|
||||||
|
"java.io.InputStream" }, value = TestClassConfiguration.Nested.class)
|
||||||
@TestConditionalOnBean(type = "java.io.OutputStream")
|
@TestConditionalOnBean(type = "java.io.OutputStream")
|
||||||
@TestConditionalOnSingleCandidate(type = "java.io.OutputStream")
|
@TestConditionalOnSingleCandidate(type = "java.io.OutputStream")
|
||||||
@TestConditionalOnWebApplication(type = Type.SERVLET)
|
@TestConditionalOnWebApplication(type = Type.SERVLET)
|
||||||
|
|
Loading…
Reference in New Issue