From e4f54a45be1a1e571721d6db8fc92a05086c4768 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 24 Sep 2018 09:27:59 -0400 Subject: [PATCH 1/5] Add AutoConfigurationImportFilter base classes Add `FilteringSpringBootCondition` base class and refactor the existing `OnClassCondition` to use it. Also update the `match` method so that the `autoConfigurationClasses` array may include `null` elements. See gh-13328 --- .../AutoConfigurationImportFilter.java | 5 +- .../AutoConfigurationImportSelector.java | 1 + .../condition/ConditionEvaluationReport.java | 15 ++ .../FilteringSpringBootCondition.java | 147 ++++++++++++++++++ .../condition/OnClassCondition.java | 139 +++-------------- 5 files changed, 184 insertions(+), 123 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportFilter.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportFilter.java index ee83eadf4da..0f6d8dc3403 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportFilter.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportFilter.java @@ -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"); * you may not use this file except in compliance with the License. @@ -45,7 +45,8 @@ public interface AutoConfigurationImportFilter { /** * Apply the filter to the given auto-configuration class candidates. * @param autoConfigurationClasses the auto-configuration classes being considered. - * Implementations should not change the values in this array. + * This array may contain {@code null} elements. Implementations should not change the + * values in this array. * @param autoConfigurationMetadata access to the meta-data generated by the * auto-configure annotation processor * @return a boolean array indicating which of the auto-configuration classes should 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 b65ff3017db..4f3532c86ae 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 @@ -263,6 +263,7 @@ public class AutoConfigurationImportSelector for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; + candidates[i] = null; skipped = true; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java index a5a09f2d7d8..cb9e5ab9d31 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java @@ -29,6 +29,7 @@ import java.util.SortedMap; import java.util.TreeMap; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; @@ -159,6 +160,20 @@ public final class ConditionEvaluationReport { return this.parent; } + /** + * Attempt to find the {@link ConditionEvaluationReport} for the specified bean + * factory. + * @param beanFactory the bean factory (may be {@code null}) + * @return the {@link ConditionEvaluationReport} or {@code null} + */ + public static ConditionEvaluationReport find(BeanFactory beanFactory) { + if (beanFactory != null && beanFactory instanceof ConfigurableBeanFactory) { + return ConditionEvaluationReport + .get((ConfigurableListableBeanFactory) beanFactory); + } + return null; + } + /** * Obtain a {@link ConditionEvaluationReport} for the specified bean factory. * @param beanFactory the bean factory diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java new file mode 100644 index 00000000000..9a6f8da7e4b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java @@ -0,0 +1,147 @@ +/* + * 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.condition; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter; +import org.springframework.boot.autoconfigure.AutoConfigurationMetadata; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; + +/** + * Abstract base class for a {@link SpringBootCondition} that also implements + * {@link AutoConfigurationImportFilter}. + * + * @author Phillip Webb + */ +abstract class FilteringSpringBootCondition extends SpringBootCondition + implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware { + + private BeanFactory beanFactory; + + private ClassLoader beanClassLoader; + + @Override + public boolean[] match(String[] autoConfigurationClasses, + AutoConfigurationMetadata autoConfigurationMetadata) { + ConditionEvaluationReport report = ConditionEvaluationReport + .find(this.beanFactory); + ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, + autoConfigurationMetadata); + boolean[] match = new boolean[outcomes.length]; + for (int i = 0; i < outcomes.length; i++) { + match[i] = (outcomes[i] == null || outcomes[i].isMatch()); + if (!match[i] && outcomes[i] != null) { + logOutcome(autoConfigurationClasses[i], outcomes[i]); + if (report != null) { + report.recordConditionEvaluation(autoConfigurationClasses[i], this, + outcomes[i]); + } + } + } + return match; + } + + protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, + AutoConfigurationMetadata autoConfigurationMetadata); + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + protected final BeanFactory getBeanFactory() { + return this.beanFactory; + } + + protected final ClassLoader getBeanClassLoader() { + return this.beanClassLoader; + } + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + protected List filter(Collection classNames, + ClassNameFilter classNameFilter, ClassLoader classLoader) { + if (CollectionUtils.isEmpty(classNames)) { + return Collections.emptyList(); + } + List matches = new ArrayList<>(classNames.size()); + for (String candidate : classNames) { + if (classNameFilter.matches(candidate, classLoader)) { + matches.add(candidate); + } + } + return matches; + } + + protected enum ClassNameFilter { + + PRESENT { + + @Override + public boolean matches(String className, ClassLoader classLoader) { + return isPresent(className, classLoader); + } + + }, + + MISSING { + + @Override + public boolean matches(String className, ClassLoader classLoader) { + return !isPresent(className, classLoader); + } + + }; + + public abstract boolean matches(String className, ClassLoader classLoader); + + public static boolean isPresent(String className, ClassLoader classLoader) { + if (classLoader == null) { + classLoader = ClassUtils.getDefaultClassLoader(); + } + try { + forName(className, classLoader); + return true; + } + catch (Throwable ex) { + return false; + } + } + + private static Class forName(String className, ClassLoader classLoader) + throws ClassNotFoundException { + if (classLoader != null) { + return classLoader.loadClass(className); + } + return Class.forName(className); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java index 4b6b535df9f..6ba4a8bef43 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java @@ -18,17 +18,10 @@ package org.springframework.boot.autoconfigure.condition; import java.security.AccessControlException; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter; import org.springframework.boot.autoconfigure.AutoConfigurationMetadata; import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style; @@ -37,7 +30,6 @@ import org.springframework.context.annotation.ConditionContext; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.util.ClassUtils; import org.springframework.util.MultiValueMap; /** @@ -49,43 +41,10 @@ import org.springframework.util.MultiValueMap; * @see ConditionalOnMissingClass */ @Order(Ordered.HIGHEST_PRECEDENCE) -class OnClassCondition extends SpringBootCondition - implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware { - - private BeanFactory beanFactory; - - private ClassLoader beanClassLoader; +class OnClassCondition extends FilteringSpringBootCondition { @Override - public boolean[] match(String[] autoConfigurationClasses, - AutoConfigurationMetadata autoConfigurationMetadata) { - ConditionEvaluationReport report = getConditionEvaluationReport(); - ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, - autoConfigurationMetadata); - boolean[] match = new boolean[outcomes.length]; - for (int i = 0; i < outcomes.length; i++) { - match[i] = (outcomes[i] == null || outcomes[i].isMatch()); - if (!match[i] && outcomes[i] != null) { - logOutcome(autoConfigurationClasses[i], outcomes[i]); - if (report != null) { - report.recordConditionEvaluation(autoConfigurationClasses[i], this, - outcomes[i]); - } - } - } - return match; - } - - private ConditionEvaluationReport getConditionEvaluationReport() { - if (this.beanFactory != null - && this.beanFactory instanceof ConfigurableBeanFactory) { - return ConditionEvaluationReport - .get((ConfigurableListableBeanFactory) this.beanFactory); - } - return null; - } - - private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, + protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { // Split the work and perform half in a background thread. Using a single // additional thread seems to offer the best performance. More threads make @@ -95,7 +54,7 @@ class OnClassCondition extends SpringBootCondition autoConfigurationClasses, 0, split, autoConfigurationMetadata); OutcomesResolver secondHalfResolver = new StandardOutcomesResolver( autoConfigurationClasses, split, autoConfigurationClasses.length, - autoConfigurationMetadata, this.beanClassLoader); + autoConfigurationMetadata, getBeanClassLoader()); ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes(); ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes(); ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length]; @@ -108,7 +67,7 @@ class OnClassCondition extends SpringBootCondition int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) { OutcomesResolver outcomesResolver = new StandardOutcomesResolver( autoConfigurationClasses, start, end, autoConfigurationMetadata, - this.beanClassLoader); + getBeanClassLoader()); try { return new ThreadedOutcomesResolver(outcomesResolver); } @@ -124,7 +83,8 @@ class OnClassCondition extends SpringBootCondition ConditionMessage matchMessage = ConditionMessage.empty(); List onClasses = getCandidates(metadata, ConditionalOnClass.class); if (onClasses != null) { - List missing = getMatches(onClasses, MatchType.MISSING, classLoader); + List missing = filter(onClasses, ClassNameFilter.MISSING, + classLoader); if (!missing.isEmpty()) { return ConditionOutcome .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class) @@ -133,12 +93,12 @@ class OnClassCondition extends SpringBootCondition } matchMessage = matchMessage.andCondition(ConditionalOnClass.class) .found("required class", "required classes").items(Style.QUOTE, - getMatches(onClasses, MatchType.PRESENT, classLoader)); + filter(onClasses, ClassNameFilter.PRESENT, classLoader)); } List onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class); if (onMissingClasses != null) { - List present = getMatches(onMissingClasses, MatchType.PRESENT, + List present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader); if (!present.isEmpty()) { return ConditionOutcome.noMatch( @@ -147,8 +107,9 @@ class OnClassCondition extends SpringBootCondition .items(Style.QUOTE, present)); } matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class) - .didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, - getMatches(onMissingClasses, MatchType.MISSING, classLoader)); + .didNotFind("unwanted class", "unwanted classes") + .items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, + classLoader)); } return ConditionOutcome.match(matchMessage); } @@ -174,72 +135,6 @@ class OnClassCondition extends SpringBootCondition } } - private List getMatches(Collection candidates, MatchType matchType, - ClassLoader classLoader) { - List matches = new ArrayList<>(candidates.size()); - for (String candidate : candidates) { - if (matchType.matches(candidate, classLoader)) { - matches.add(candidate); - } - } - return matches; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.beanClassLoader = classLoader; - } - - private enum MatchType { - - PRESENT { - - @Override - public boolean matches(String className, ClassLoader classLoader) { - return isPresent(className, classLoader); - } - - }, - - MISSING { - - @Override - public boolean matches(String className, ClassLoader classLoader) { - return !isPresent(className, classLoader); - } - - }; - - private static boolean isPresent(String className, ClassLoader classLoader) { - if (classLoader == null) { - classLoader = ClassUtils.getDefaultClassLoader(); - } - try { - forName(className, classLoader); - return true; - } - catch (Throwable ex) { - return false; - } - } - - private static Class forName(String className, ClassLoader classLoader) - throws ClassNotFoundException { - if (classLoader != null) { - return classLoader.loadClass(className); - } - return Class.forName(className); - } - - public abstract boolean matches(String className, ClassLoader classLoader); - - } - private interface OutcomesResolver { ConditionOutcome[] resolveOutcomes(); @@ -304,10 +199,12 @@ class OnClassCondition extends SpringBootCondition ConditionOutcome[] outcomes = new ConditionOutcome[end - start]; for (int i = start; i < end; i++) { String autoConfigurationClass = autoConfigurationClasses[i]; - Set candidates = autoConfigurationMetadata - .getSet(autoConfigurationClass, "ConditionalOnClass"); - if (candidates != null) { - outcomes[i - start] = getOutcome(candidates); + if (autoConfigurationClass != null) { + Set candidates = autoConfigurationMetadata + .getSet(autoConfigurationClass, "ConditionalOnClass"); + if (candidates != null) { + outcomes[i - start] = getOutcome(candidates); + } } } return outcomes; @@ -315,7 +212,7 @@ class OnClassCondition extends SpringBootCondition private ConditionOutcome getOutcome(Set candidates) { try { - List missing = getMatches(candidates, MatchType.MISSING, + List missing = filter(candidates, ClassNameFilter.MISSING, this.beanClassLoader); if (!missing.isEmpty()) { return ConditionOutcome.noMatch( From 586507c49a36eec07769fdbc86ca84b30164ebd9 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 21 Sep 2018 13:55:37 -0700 Subject: [PATCH 2/5] Generate auto-configuration OnBean data Update the auto-configuration annotation processor to generate properties for `@ConditionalOnBean` and `@ConditionalOnSingleCandidate`. See gh-13328 --- .../AutoConfigureAnnotationProcessor.java | 144 +++++++++++------- .../boot/autoconfigureprocessor/Elements.java | 60 ++++++++ ...AutoConfigureAnnotationProcessorTests.java | 36 ++++- .../TestClassConfiguration.java | 4 +- ...tConditionMetadataAnnotationProcessor.java | 7 +- .../TestConditionalOnBean.java | 45 ++++++ .../TestConditionalOnSingleCandidate.java | 40 +++++ .../TestOnBeanWithNameClassConfiguration.java | 28 ++++ 8 files changed, 303 insertions(+), 61 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/Elements.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnBean.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnSingleCandidate.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestOnBeanWithNameClassConfiguration.java 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 6c6a8937258..e6bad9588a4 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 @@ -18,14 +18,15 @@ package org.springframework.boot.autoconfigureprocessor; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Properties; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.processing.AbstractProcessor; @@ -36,10 +37,8 @@ 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.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; import javax.tools.FileObject; import javax.tools.StandardLocation; @@ -52,6 +51,8 @@ import javax.tools.StandardLocation; */ @SupportedAnnotationTypes({ "org.springframework.context.annotation.Configuration", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass", + "org.springframework.boot.autoconfigure.condition.ConditionalOnBean", + "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate", "org.springframework.boot.autoconfigure.AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureOrder" }) @@ -60,7 +61,9 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor { protected static final String PROPERTIES_PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; - private Map annotations; + private final Map annotations; + + private final Map valueExtractors; private final Properties properties = new Properties(); @@ -68,6 +71,9 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor { Map annotations = new LinkedHashMap<>(); addAnnotations(annotations); this.annotations = Collections.unmodifiableMap(annotations); + Map valueExtractors = new LinkedHashMap<>(); + addValueExtractors(valueExtractors); + this.valueExtractors = Collections.unmodifiableMap(valueExtractors); } protected void addAnnotations(Map annotations) { @@ -75,6 +81,10 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor { "org.springframework.context.annotation.Configuration"); annotations.put("ConditionalOnClass", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass"); + annotations.put("ConditionalOnBean", + "org.springframework.boot.autoconfigure.condition.ConditionalOnBean"); + annotations.put("ConditionalOnSingleCandidate", + "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate"); annotations.put("AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureBefore"); annotations.put("AutoConfigureAfter", @@ -83,6 +93,17 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor { "org.springframework.boot.autoconfigure.AutoConfigureOrder"); } + private void addValueExtractors(Map attributes) { + attributes.put("Configuration", ValueExtractor.allFrom("value")); + attributes.put("ConditionalOnClass", ValueExtractor.allFrom("value", "name")); + attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor()); + attributes.put("ConditionalOnSingleCandidate", + new OnBeanConditionValueExtractor()); + attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name")); + attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name")); + attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value")); + } + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); @@ -123,10 +144,10 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor { private void processElement(Element element, String propertyKey, String annotationName) { try { - String qualifiedName = getQualifiedName(element); + String qualifiedName = Elements.getQualifiedName(element); AnnotationMirror annotation = getAnnotation(element, annotationName); if (qualifiedName != null && annotation != null) { - List values = getValues(annotation); + List values = getValues(propertyKey, annotation); this.properties.put(qualifiedName + "." + propertyKey, toCommaDelimitedString(values)); this.properties.put(qualifiedName, ""); @@ -158,68 +179,89 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor { return result.toString(); } - private List getValues(AnnotationMirror annotation) { - return annotation.getElementValues().entrySet().stream() - .filter(this::isNameOrValueAttribute).flatMap(this::getValues) - .collect(Collectors.toList()); - } - - private boolean isNameOrValueAttribute(Entry entry) { - String attributeName = entry.getKey().getSimpleName().toString(); - return "name".equals(attributeName) || "value".equals(attributeName); + private List getValues(String propertyKey, AnnotationMirror annotation) { + ValueExtractor extractor = this.valueExtractors.get(propertyKey); + if (extractor == null) { + return Collections.emptyList(); + } + return extractor.getValues(annotation); } - @SuppressWarnings("unchecked") - private Stream getValues(Entry entry) { - Object value = entry.getValue().getValue(); - if (value instanceof List) { - return ((List) value).stream() - .map((annotation) -> processValue(annotation.getValue())); + private void writeProperties() throws IOException { + if (!this.properties.isEmpty()) { + FileObject file = this.processingEnv.getFiler() + .createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH); + try (OutputStream outputStream = file.openOutputStream()) { + this.properties.store(outputStream, null); + } } - return Stream.of(processValue(value)); } - private Object processValue(Object value) { - if (value instanceof DeclaredType) { - return getQualifiedName(((DeclaredType) value).asElement()); + @FunctionalInterface + private interface ValueExtractor { + + List getValues(AnnotationMirror annotation); + + static ValueExtractor allFrom(String... attributes) { + Set names = new HashSet<>(Arrays.asList(attributes)); + return new AbstractValueExtractor() { + + @Override + public List getValues(AnnotationMirror annotation) { + List result = new ArrayList<>(); + annotation.getElementValues().forEach((key, value) -> { + if (names.contains(key.getSimpleName().toString())) { + extractValues(value).forEach(result::add); + } + }); + return result; + } + + }; } - return value; + } - private String getQualifiedName(Element element) { - if (element != null) { - TypeElement enclosingElement = getEnclosingTypeElement(element.asType()); - if (enclosingElement != null) { - return getQualifiedName(enclosingElement) + "$" - + ((DeclaredType) element.asType()).asElement().getSimpleName() - .toString(); + private abstract static class AbstractValueExtractor implements ValueExtractor { + + @SuppressWarnings("unchecked") + protected Stream extractValues(AnnotationValue annotationValue) { + if (annotationValue == null) { + return Stream.empty(); } - if (element instanceof TypeElement) { - return ((TypeElement) element).getQualifiedName().toString(); + Object value = annotationValue.getValue(); + if (value instanceof List) { + return ((List) value).stream() + .map((annotation) -> extractValue(annotation.getValue())); } + return Stream.of(extractValue(value)); } - return null; - } - private TypeElement getEnclosingTypeElement(TypeMirror type) { - if (type instanceof DeclaredType) { - DeclaredType declaredType = (DeclaredType) type; - Element enclosingElement = declaredType.asElement().getEnclosingElement(); - if (enclosingElement != null && enclosingElement instanceof TypeElement) { - return (TypeElement) enclosingElement; + private Object extractValue(Object value) { + if (value instanceof DeclaredType) { + return Elements.getQualifiedName(((DeclaredType) value).asElement()); } + return value; } - return null; + } - private void writeProperties() throws IOException { - if (!this.properties.isEmpty()) { - FileObject file = this.processingEnv.getFiler() - .createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH); - try (OutputStream outputStream = file.openOutputStream()) { - this.properties.store(outputStream, null); + private static class OnBeanConditionValueExtractor extends AbstractValueExtractor { + + @Override + public List getValues(AnnotationMirror annotation) { + Map attributes = new LinkedHashMap<>(); + annotation.getElementValues().forEach((key, value) -> attributes + .put(key.getSimpleName().toString(), value)); + if (attributes.containsKey("name")) { + return Collections.emptyList(); } + List result = new ArrayList<>(); + extractValues(attributes.get("value")).forEach(result::add); + extractValues(attributes.get("type")).forEach(result::add); + return result; } + } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/Elements.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/Elements.java new file mode 100644 index 00000000000..914c9beeea0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/Elements.java @@ -0,0 +1,60 @@ +/* + * 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.autoconfigureprocessor; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + +/** + * Utilities for dealing with {@link Element} classes. + * + * @author Phillip Webb + */ +final class Elements { + + private Elements() { + } + + static String getQualifiedName(Element element) { + if (element != null) { + TypeElement enclosingElement = getEnclosingTypeElement(element.asType()); + if (enclosingElement != null) { + return getQualifiedName(enclosingElement) + "$" + + ((DeclaredType) element.asType()).asElement().getSimpleName() + .toString(); + } + if (element instanceof TypeElement) { + return ((TypeElement) element).getQualifiedName().toString(); + } + } + return null; + } + + private static TypeElement getEnclosingTypeElement(TypeMirror type) { + if (type instanceof DeclaredType) { + DeclaredType declaredType = (DeclaredType) type; + Element enclosingElement = declaredType.asElement().getEnclosingElement(); + if (enclosingElement != null && enclosingElement instanceof TypeElement) { + return (TypeElement) enclosingElement; + } + } + return null; + } + +} 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 929960d087f..716bd8ab2f5 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 @@ -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"); * you may not use this file except in compliance with the License. @@ -50,18 +50,38 @@ public class AutoConfigureAnnotationProcessorTests { @Test public void annotatedClass() throws Exception { Properties properties = compile(TestClassConfiguration.class); - assertThat(properties).hasSize(3); + assertThat(properties).hasSize(5); assertThat(properties).containsEntry( "org.springframework.boot.autoconfigureprocessor." + "TestClassConfiguration.ConditionalOnClass", "java.io.InputStream,org.springframework.boot.autoconfigureprocessor." + "TestClassConfiguration$Nested"); - assertThat(properties).containsKey( - "org.springframework.boot.autoconfigureprocessor.TestClassConfiguration"); - assertThat(properties).containsKey( - "org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.Configuration"); - assertThat(properties).doesNotContainKey( - "org.springframework.boot.autoconfigureprocessor.TestClassConfiguration$Nested"); + assertThat(properties) + .containsKey("org.springframework.boot.autoconfigureprocessor." + + "TestClassConfiguration"); + assertThat(properties) + .containsKey("org.springframework.boot.autoconfigureprocessor." + + "TestClassConfiguration.Configuration"); + assertThat(properties) + .doesNotContainKey("org.springframework.boot.autoconfigureprocessor." + + "TestClassConfiguration$Nested"); + assertThat(properties).containsEntry( + "org.springframework.boot.autoconfigureprocessor." + + "TestClassConfiguration.ConditionalOnBean", + "java.io.OutputStream"); + assertThat(properties).containsEntry( + "org.springframework.boot.autoconfigureprocessor." + + "TestClassConfiguration.ConditionalOnSingleCandidate", + "java.io.OutputStream"); + } + + @Test + public void annoatedClassWithOnBeanThatHasName() throws Exception { + Properties properties = compile(TestOnBeanWithNameClassConfiguration.class); + assertThat(properties).hasSize(3); + assertThat(properties).containsEntry( + "org.springframework.boot.autoconfigureprocessor.TestOnBeanWithNameClassConfiguration.ConditionalOnBean", + ""); } @Test diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestClassConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestClassConfiguration.java index 3bc346b1ecb..6cd74336095 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestClassConfiguration.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestClassConfiguration.java @@ -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"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ package org.springframework.boot.autoconfigureprocessor; */ @TestConfiguration @TestConditionalOnClass(name = "java.io.InputStream", value = TestClassConfiguration.Nested.class) +@TestConditionalOnBean(type = "java.io.OutputStream") +@TestConditionalOnSingleCandidate(type = "java.io.OutputStream") public class TestClassConfiguration { @TestAutoConfigureOrder diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionMetadataAnnotationProcessor.java index 56471007965..178d8e793f6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionMetadataAnnotationProcessor.java @@ -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"); * you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ import javax.annotation.processing.SupportedAnnotationTypes; @SupportedAnnotationTypes({ "org.springframework.boot.autoconfigureprocessor.TestConfiguration", "org.springframework.boot.autoconfigureprocessor.TestConditionalOnClass", + "org.springframework.boot.autoconfigure.condition.TestConditionalOnBean", + "org.springframework.boot.autoconfigure.condition.TestConditionalOnSingleCandidate", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigureBefore", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigureAfter", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigureOrder" }) @@ -48,6 +50,9 @@ public class TestConditionMetadataAnnotationProcessor protected void addAnnotations(Map annotations) { put(annotations, "Configuration", TestConfiguration.class); put(annotations, "ConditionalOnClass", TestConditionalOnClass.class); + put(annotations, "ConditionalOnBean", TestConditionalOnBean.class); + put(annotations, "ConditionalOnSingleCandidate", + TestConditionalOnSingleCandidate.class); put(annotations, "AutoConfigureBefore", TestAutoConfigureBefore.class); put(annotations, "AutoConfigureAfter", TestAutoConfigureAfter.class); put(annotations, "AutoConfigureOrder", TestAutoConfigureOrder.class); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnBean.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnBean.java new file mode 100644 index 00000000000..7c22b99abd1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnBean.java @@ -0,0 +1,45 @@ +/* + * 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.autoconfigureprocessor; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Alternative to Spring Boot's {@code ConditionalOnBean} for testing (removes the need + * for a dependency on the real annotation). + * + * @author Phillip Webb + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface TestConditionalOnBean { + + Class[] value() default {}; + + String[] type() default {}; + + Class[] annotation() default {}; + + String[] name() default {}; + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnSingleCandidate.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnSingleCandidate.java new file mode 100644 index 00000000000..51e103e7258 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnSingleCandidate.java @@ -0,0 +1,40 @@ +/* + * 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.autoconfigureprocessor; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Alternative to Spring Boot's {@code ConditionalOnSingleCandidate} for testing (removes + * the need for a dependency on the real annotation). + * + * @author Phillip Webb + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface TestConditionalOnSingleCandidate { + + Class value() default Object.class; + + String type() default ""; + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestOnBeanWithNameClassConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestOnBeanWithNameClassConfiguration.java new file mode 100644 index 00000000000..3f4f87e9bd8 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestOnBeanWithNameClassConfiguration.java @@ -0,0 +1,28 @@ +/* + * 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.autoconfigureprocessor; + +/** + * Test configuration with an annotated class. + * + * @author Phillip Webb + */ +@TestConfiguration +@TestConditionalOnBean(name = "test", type = "java.io.OutputStream") +public class TestOnBeanWithNameClassConfiguration { + +} From c2f8398c06a7ffb22d95c8cba50d9bdcd20de849 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 24 Sep 2018 09:30:07 -0400 Subject: [PATCH 3/5] Allow early OnBeanCondition filtering Update `OnBeanCondition` to be an `AutoConfigurationImportFilter` and filter out classes early. See gh-13328 --- .../condition/OnBeanCondition.java | 39 ++++++++++++++++++- .../main/resources/META-INF/spring.factories | 1 + 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index 5da7222d54a..fc49d7a93b1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -34,6 +34,7 @@ import org.springframework.beans.factory.HierarchicalBeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigurationMetadata; import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; @@ -58,7 +59,8 @@ import org.springframework.util.StringUtils; * @author Andy Wilkinson */ @Order(Ordered.LOWEST_PRECEDENCE) -class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition { +class OnBeanCondition extends FilteringSpringBootCondition + implements ConfigurationCondition { /** * Bean definition attribute name for factory beans to signal their product type (if @@ -66,6 +68,40 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit */ public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE; + @Override + protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, + AutoConfigurationMetadata autoConfigurationMetadata) { + ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length]; + for (int i = 0; i < outcomes.length; i++) { + String autoConfigurationClass = autoConfigurationClasses[i]; + if (autoConfigurationClass != null) { + Set onBeanTypes = autoConfigurationMetadata + .getSet(autoConfigurationClass, "ConditionalOnBean"); + outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class); + if (outcomes[i] == null) { + Set onSingleCandidateTypes = autoConfigurationMetadata.getSet( + autoConfigurationClass, "ConditionalOnSingleCandidate"); + outcomes[i] = getOutcome(onSingleCandidateTypes, + ConditionalOnSingleCandidate.class); + } + } + } + return outcomes; + } + + private ConditionOutcome getOutcome(Set requiredBeanTypes, + Class annotation) { + List missing = filter(requiredBeanTypes, ClassNameFilter.MISSING, + getBeanClassLoader()); + if (!missing.isEmpty()) { + ConditionMessage message = ConditionMessage.forCondition(annotation) + .didNotFind("required type", "required types") + .items(Style.QUOTE, missing); + return ConditionOutcome.noMatch(message); + } + return null; + } + @Override public ConfigurationPhase getConfigurationPhase() { return ConfigurationPhase.REGISTER_BEAN; @@ -337,7 +373,6 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit .getParentBeanFactory()), beanName, considerHierarchy); } return null; - } private static class BeanSearchSpec { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 5a80d1196aa..df2d99ab37f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -17,6 +17,7 @@ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoCo # Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ +org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition # Auto Configure From 75bde003348fc29ba5399563787dbf33534ddefb Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 21 Sep 2018 17:40:33 -0700 Subject: [PATCH 4/5] Generate auto-configuration OnWebApplication data Update the auto-configuration annotation processor to generate properties for `@OnWebApplication`. See gh-13328 --- .../AutoConfigureAnnotationProcessor.java | 4 ++ ...AutoConfigureAnnotationProcessorTests.java | 8 +++- ...TestAutoConfigureAnnotationProcessor.java} | 7 ++- .../TestClassConfiguration.java | 3 ++ .../TestConditionalOnWebApplication.java | 44 +++++++++++++++++++ 5 files changed, 62 insertions(+), 4 deletions(-) rename spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/{TestConditionMetadataAnnotationProcessor.java => TestAutoConfigureAnnotationProcessor.java} (90%) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnWebApplication.java 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 e6bad9588a4..2095eb80411 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 @@ -53,6 +53,7 @@ import javax.tools.StandardLocation; "org.springframework.boot.autoconfigure.condition.ConditionalOnClass", "org.springframework.boot.autoconfigure.condition.ConditionalOnBean", "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate", + "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication", "org.springframework.boot.autoconfigure.AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureOrder" }) @@ -85,6 +86,8 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor { "org.springframework.boot.autoconfigure.condition.ConditionalOnBean"); annotations.put("ConditionalOnSingleCandidate", "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate"); + annotations.put("ConditionalOnWebApplication", + "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication"); annotations.put("AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureBefore"); annotations.put("AutoConfigureAfter", @@ -99,6 +102,7 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor { attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor()); attributes.put("ConditionalOnSingleCandidate", new OnBeanConditionValueExtractor()); + attributes.put("ConditionalOnWebApplication", ValueExtractor.allFrom("type")); attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name")); attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name")); attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value")); 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 716bd8ab2f5..acf14febef4 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 @@ -50,7 +50,7 @@ public class AutoConfigureAnnotationProcessorTests { @Test public void annotatedClass() throws Exception { Properties properties = compile(TestClassConfiguration.class); - assertThat(properties).hasSize(5); + assertThat(properties).hasSize(6); assertThat(properties).containsEntry( "org.springframework.boot.autoconfigureprocessor." + "TestClassConfiguration.ConditionalOnClass", @@ -73,6 +73,10 @@ public class AutoConfigureAnnotationProcessorTests { "org.springframework.boot.autoconfigureprocessor." + "TestClassConfiguration.ConditionalOnSingleCandidate", "java.io.OutputStream"); + assertThat(properties).containsEntry( + "org.springframework.boot.autoconfigureprocessor." + + "TestClassConfiguration.ConditionalOnWebApplication", + "SERVLET"); } @Test @@ -124,7 +128,7 @@ public class AutoConfigureAnnotationProcessorTests { } private Properties compile(Class... types) throws IOException { - TestConditionMetadataAnnotationProcessor processor = new TestConditionMetadataAnnotationProcessor( + TestAutoConfigureAnnotationProcessor processor = new TestAutoConfigureAnnotationProcessor( this.compiler.getOutputLocation()); this.compiler.getTask(types).call(processor); return processor.getWrittenProperties(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestAutoConfigureAnnotationProcessor.java similarity index 90% rename from spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionMetadataAnnotationProcessor.java rename to spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestAutoConfigureAnnotationProcessor.java index 178d8e793f6..1e2cd1470bc 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestAutoConfigureAnnotationProcessor.java @@ -34,15 +34,16 @@ import javax.annotation.processing.SupportedAnnotationTypes; "org.springframework.boot.autoconfigureprocessor.TestConditionalOnClass", "org.springframework.boot.autoconfigure.condition.TestConditionalOnBean", "org.springframework.boot.autoconfigure.condition.TestConditionalOnSingleCandidate", + "org.springframework.boot.autoconfigure.condition.TestConditionalOnWebApplication", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigureBefore", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigureAfter", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigureOrder" }) -public class TestConditionMetadataAnnotationProcessor +public class TestAutoConfigureAnnotationProcessor extends AutoConfigureAnnotationProcessor { private final File outputLocation; - public TestConditionMetadataAnnotationProcessor(File outputLocation) { + public TestAutoConfigureAnnotationProcessor(File outputLocation) { this.outputLocation = outputLocation; } @@ -53,6 +54,8 @@ public class TestConditionMetadataAnnotationProcessor put(annotations, "ConditionalOnBean", TestConditionalOnBean.class); put(annotations, "ConditionalOnSingleCandidate", TestConditionalOnSingleCandidate.class); + put(annotations, "ConditionalOnWebApplication", + TestConditionalOnWebApplication.class); put(annotations, "AutoConfigureBefore", TestAutoConfigureBefore.class); put(annotations, "AutoConfigureAfter", TestAutoConfigureAfter.class); put(annotations, "AutoConfigureOrder", TestAutoConfigureOrder.class); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestClassConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestClassConfiguration.java index 6cd74336095..5bb32cb5b69 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestClassConfiguration.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestClassConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigureprocessor; +import org.springframework.boot.autoconfigureprocessor.TestConditionalOnWebApplication.Type; + /** * Test configuration with an annotated class. * @@ -25,6 +27,7 @@ package org.springframework.boot.autoconfigureprocessor; @TestConditionalOnClass(name = "java.io.InputStream", value = TestClassConfiguration.Nested.class) @TestConditionalOnBean(type = "java.io.OutputStream") @TestConditionalOnSingleCandidate(type = "java.io.OutputStream") +@TestConditionalOnWebApplication(type = Type.SERVLET) public class TestClassConfiguration { @TestAutoConfigureOrder diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnWebApplication.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnWebApplication.java new file mode 100644 index 00000000000..b340da7d0df --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionalOnWebApplication.java @@ -0,0 +1,44 @@ +/* + * 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.autoconfigureprocessor; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Alternative to Spring Boot's {@code @ConditionalOnWebApplication} for testing (removes + * the need for a dependency on the real annotation). + * + * @author Phillip Webb + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface TestConditionalOnWebApplication { + + Type type() default Type.ANY; + + enum Type { + + ANY, SERVLET, REACTIVE + + } + +} From ff98ba0fa569b4d025ce9ad4532dd6b65800274b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 24 Sep 2018 09:30:18 -0400 Subject: [PATCH 5/5] Allow early OnWebApplication filtering Update `OnWebApplicationCondition` to be an `AutoConfigurationImportFilter` and filter out classes early. Closes gh-13328 --- .../condition/OnWebApplicationCondition.java | 65 +++++++++++++++++-- .../main/resources/META-INF/spring.factories | 3 +- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java index 19fdb00cf92..8dc296e4c03 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java @@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.condition; import java.util.Map; +import org.springframework.boot.autoconfigure.AutoConfigurationMetadata; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebEnvironment; import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext; @@ -36,14 +37,60 @@ import org.springframework.web.context.WebApplicationContext; * {@link WebApplicationContext}. * * @author Dave Syer + * @author Phillip Webb * @see ConditionalOnWebApplication * @see ConditionalOnNotWebApplication */ @Order(Ordered.HIGHEST_PRECEDENCE + 20) -class OnWebApplicationCondition extends SpringBootCondition { +class OnWebApplicationCondition extends FilteringSpringBootCondition { - private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context." - + "support.GenericWebApplicationContext"; + private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext"; + + private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult"; + + @Override + protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, + AutoConfigurationMetadata autoConfigurationMetadata) { + ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length]; + for (int i = 0; i < outcomes.length; i++) { + String autoConfigurationClass = autoConfigurationClasses[i]; + if (autoConfigurationClass != null) { + outcomes[i] = getOutcome(autoConfigurationMetadata + .get(autoConfigurationClass, "ConditionalOnWebApplication")); + } + } + return outcomes; + } + + private ConditionOutcome getOutcome(String type) { + if (type == null) { + return null; + } + ConditionMessage.Builder message = ConditionMessage + .forCondition(ConditionalOnWebApplication.class); + if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) { + if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, + getBeanClassLoader())) { + return ConditionOutcome.noMatch( + message.didNotFind("servlet web application classes").atAll()); + } + } + if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) { + if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, + getBeanClassLoader())) { + return ConditionOutcome.noMatch( + message.didNotFind("reactive web application classes").atAll()); + } + } + if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, + getBeanClassLoader()) + && !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, + getBeanClassLoader())) { + return ConditionOutcome.noMatch(message + .didNotFind("reactive or servlet web application classes").atAll()); + } + return null; + } @Override public ConditionOutcome getMatchOutcome(ConditionContext context, @@ -93,9 +140,10 @@ class OnWebApplicationCondition extends SpringBootCondition { private ConditionOutcome isServletWebApplication(ConditionContext context) { ConditionMessage.Builder message = ConditionMessage.forCondition(""); - if (!ClassUtils.isPresent(WEB_CONTEXT_CLASS, context.getClassLoader())) { - return ConditionOutcome - .noMatch(message.didNotFind("web application classes").atAll()); + if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, + context.getClassLoader())) { + return ConditionOutcome.noMatch( + message.didNotFind("servlet web application classes").atAll()); } if (context.getBeanFactory() != null) { String[] scopes = context.getBeanFactory().getRegisteredScopeNames(); @@ -115,6 +163,11 @@ class OnWebApplicationCondition extends SpringBootCondition { private ConditionOutcome isReactiveWebApplication(ConditionContext context) { ConditionMessage.Builder message = ConditionMessage.forCondition(""); + if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, + context.getClassLoader())) { + return ConditionOutcome.noMatch( + message.didNotFind("reactive web application classes").atAll()); + } if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) { return ConditionOutcome .match(message.foundExactly("ConfigurableReactiveWebEnvironment")); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index df2d99ab37f..6ede25c0412 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -18,7 +18,8 @@ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoCo # Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ -org.springframework.boot.autoconfigure.condition.OnClassCondition +org.springframework.boot.autoconfigure.condition.OnClassCondition,\ +org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\