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:
Phillip Webb
2018-10-10 21:15:32 -07:00
parent b1d4cf4ea8
commit 85f86243c9
5 changed files with 83 additions and 37 deletions

View File

@@ -21,6 +21,7 @@ import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -98,7 +99,7 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
private void addValueExtractors(Map<String, ValueExtractor> attributes) {
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("ConditionalOnSingleCandidate",
new OnBeanConditionValueExtractor());
@@ -206,22 +207,8 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
List<Object> getValues(AnnotationMirror annotation);
static ValueExtractor allFrom(String... attributes) {
Set<String> names = new HashSet<>(Arrays.asList(attributes));
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;
}
};
static ValueExtractor allFrom(String... names) {
return new NamedValuesExtractor(names);
}
}
@@ -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 {
@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");
}
}
}

View File

@@ -55,7 +55,7 @@ public class AutoConfigureAnnotationProcessorTests {
"org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration.ConditionalOnClass",
"java.io.InputStream,org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration$Nested");
+ "TestClassConfiguration$Nested,org.springframework.foo");
assertThat(properties)
.containsKey("org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration");

View File

@@ -24,7 +24,8 @@ import org.springframework.boot.autoconfigureprocessor.TestConditionalOnWebAppli
* @author Madhura Bhave
*/
@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")
@TestConditionalOnSingleCandidate(type = "java.io.OutputStream")
@TestConditionalOnWebApplication(type = Type.SERVLET)