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 ee83eadf4d..0f6d8dc340 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 b65ff3017d..4f3532c86a 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 a5a09f2d7d..cb9e5ab9d3 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 0000000000..9a6f8da7e4 --- /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 4b6b535df9..6ba4a8bef4 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(