Condition
true is returned if the condition is met, and false is returned if the condition is not met
@FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
Several sub interfaces and classes
Condition has several sub interfaces and classes, and its functions are extended
ConfigurationCondition
public interface ConfigurationCondition extends Condition { ConfigurationPhase getConfigurationPhase(); enum ConfigurationPhase { PARSE_CONFIGURATION, REGISTER_BEAN } }
The ConfigurationCondition interface adds a method to describe the Condition stage. The matches method will be called only if the stage calling the Condition matches the stage of the Condition. ConfigurationPhase defines two phases:
PARSE_CONFIGURATION: configuration class (@ configuration) parsing phase
REGISTER_BEAN: the registration phase of ordinary beans (including the registration of configuration classes)
SpringBootConfiguration
public abstract class SpringBootCondition implements Condition { private final Log logger = LogFactory.getLog(getClass()); @Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName = getClassOrMethodName(metadata); try { ConditionOutcome outcome = getMatchOutcome(context, metadata); logOutcome(classOrMethodName, outcome); recordEvaluation(context, classOrMethodName, outcome); return outcome.isMatch(); } catch (NoClassDefFoundError ex) { throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + ex.getMessage() + " not found. Make sure your own configuration does not rely on " + "that class. This can also happen if you are " + "@ComponentScanning a springframework package (e.g. if you " + "put a @ComponentScan in the default package by mistake)", ex); } catch (RuntimeException ex) { throw new IllegalStateException("Error processing condition on " + getName(metadata), ex); } } }
SpringBootCondition is an abstract class. It implements the interface method using the template pattern and does three things:
1. getMatchOutcome generates ConditionOutcome, which is an abstract method (the only method that needs subclass Implementation)
2. Level trace
3. Call ConditionEvaluationReport to report the results
ConditionOutcome records the matching result and message
public class ConditionOutcome { private final boolean match; private final ConditionMessage message; }
FilteringSpringBootCondition
Class definition
abstract class FilteringSpringBootCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware
Filteringsprinbootcondition extends the spring bootcondition and implements the AutoConfigurationImportFilter interface. Extending the SpringBootCondition interface does not implement its abstract method, which is exactly the same as SpringBootCondition; You need to take a look at the part of implementing the AutoConfigurationImportFilter interface
AutoConfigurationImportFilter
@FunctionalInterface public interface AutoConfigurationImportFilter { boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata); }
What is the AutoConfigurationImportFilter interface? SpringBoot will write the AutoConfiguration configuration class in spring In the factories file, it will be written completely. In fact, most of them are not used in the project, and a large part needs to be filtered out in advance. The autoConfigurationClasses passed in are the classes that need to be filtered, and the boolean [] array is returned, that is, whether the condition of each class matches. If it is true, the condition matches, and the AutoConfiguration class should be retained; If false, the condition does not match, and the AutoConfiguration class should be filtered out
Filteringsprinbootcondition implementation
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; } }
Filteringsprinbootcondition implements the match method of the AutoConfigurationImportFilter interface. In fact, it also adopts the template mode and does four things
1. Call the abstract method getOutcomes to obtain the ConditionOutcome array of each automatic configuration class
2. The assembly in the for loop returns the result: outcome is null or outcome isMatch. This class is configured with match
3. Log
4. Report results
Here is another important method filter in this class, which is used in many places later
The filter method will pass in a group of class names classNames and classNameFilter to return the class that meets the classNameFilter condition
protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter, ClassLoader classLoader) { if (CollectionUtils.isEmpty(classNames)) { return Collections.emptyList(); } List<String> matches = new ArrayList<>(classNames.size()); for (String candidate : classNames) { if (classNameFilter.matches(candidate, classLoader)) { matches.add(candidate); } } return matches; }
ClassNameFilter is an enumeration, including PRESENT and MISSING
The isPresent method in ClassNameFilter is to call class Forname judges whether this class exists. By looking at the matches methods implemented by the two enumerations, we can know that enumeration PRESENT returns true when the class exists and enumeration MISSING returns true when the class does not exist
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); } }; abstract boolean matches(String className, ClassLoader classLoader); static boolean isPresent(String className, ClassLoader classLoader) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } try { resolve(className, classLoader); return true; } catch (Throwable ex) { return false; } } } protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException { if (classLoader != null) { return Class.forName(className, false, classLoader); } return Class.forName(className); }
Therefore, the use of filter (collection classNames, ClassNameFilter, ClassNameFilter, classloader, classloader) method is: when ClassNameFilter passes PRESENT, it returns the classes existing in classNames; ClassNameFilter returns a class that does not exist in classNames when passing MISSING
Where is it used
Configuration class resolution & bean registration
1,ConfigurationClassParser#processConfigurationClass
ConfigurationClassParser when parsing configuration classes
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ... }
2. When ConfigurationClassParser#doProcessConfigurationClass processes the ComponentScan annotation of the configuration class
protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { ... // Process any @ComponentScan annotations Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } ... }
3. ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod when registering @ Bean
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { ... if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) { configClass.skippedBeanMethods.add(methodName); return; } ... }
4. AnnotatedBeanDefinitionReader#doRegisterBean when registering a bean
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } }
In these usage scenarios, the ConditionEvaluator is used to call the Condition to obtain the match result, mainly the shouldSkip method. Returning true means that the conditions are not met, and the configuration class needs to be skipped or the bean does not need to be registered
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } // 1 if (phase == null) { if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } // 2 List<Condition> conditions = new ArrayList<>(); for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } // 3 AnnotationAwareOrderComparator.sort(conditions); // 4 for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }
Implementation details
1. If phase is null, the current phase is inferred. If this class is a configuration class, phase is PARSE_CONFIGURATION, otherwise REGISTER_BEAN
How to judge whether it is a configuration class? In fact, it depends on whether specific annotations are marked and whether there are @ bean methods
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { return false; } // Any of the typical annotations found? // @Component,@ComponentScan,@Import,@ImportResource for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // Finally, let's look for @Bean methods... return hasBeanMethods(metadata); }
2. Get the Condition class and instantiate it
The Condition class is obtained from @ Conditional and its derived annotations; Instantiation is realized through reflection calling construction method. When a bean is configured or registered on a configuration class, multiple conditions may be specified, all of which are a List
3. Sort conditions
Call AnnotationAwareOrderComparator to sort conditions. AnnotationAwareOrderComparator can take PriorityOrdered interface, Ordered interface and the Order specified by @ Order into account
4. Call this set of conditions
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata))
The for loop calls this set of conditions. Before calling the conditions, it will judge whether the phase matches: if the condition does not specify a phase (directly implements the condition interface but does not implement the configurationcondition interface) or the phase of the condition is the same as the current phase, the condition interface will be called.
And as long as one condition is not met, it will return true, indicating that the configuration class needs to be skipped or the bean does not need to be registered; false is returned only when all conditions are met
AutoConfiguration filter AutoConfiguration classes
Spring. Of AutoConfiguration Many automatic configuration classes will be written in factories, and only a few will be used in actual projects. Therefore, the configuration classes read will be filtered out first. The main logic is in the AutoConfigurationImportSelector#getAutoConfigurationEntry method, and getcandidateconfigurations (annotation metadata, attributes) through spring Factories SPI gets all the AutoConfiguration class names. Here's how to filter
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
1. getConfigurationClassFilter() get Filter
private ConfigurationClassFilter getConfigurationClassFilter() { if (this.configurationClassFilter == null) { List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters(); for (AutoConfigurationImportFilter filter : filters) { invokeAwareMethods(filter); } this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters); } return this.configurationClassFilter; } protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); }
Here, the AutoConfigurationImportFilter instance is obtained through the spring factoriesloader, and then encapsulated into the ConfigurationClassFilter object
spring. What is configured in factories? In fact, three conditions are configured, OnBeanCondition, OnClassCondition and OnWebApplicationCondition. It can also be expanded by itself
# 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.OnWebApplicationCondition
What is ConfigurationClassFilter? In fact, I saved the configured AutoConfigurationImportFilter
private static class ConfigurationClassFilter { private final AutoConfigurationMetadata autoConfigurationMetadata; private final List<AutoConfigurationImportFilter> filters; }
2,AutoConfigurationImportSelector.ConfigurationClassFilter#filter filters out those that do not meet the conditions
List<String> filter(List<String> configurations) { long startTime = System.nanoTime(); String[] candidates = StringUtils.toStringArray(configurations); boolean skipped = false; for (AutoConfigurationImportFilter filter : this.filters) { boolean[] match = filter.match(candidates, this.autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { if (!match[i]) { candidates[i] = null; skipped = true; } } } if (!skipped) { return configurations; } List<String> result = new ArrayList<>(candidates.length); for (String candidate : candidates) { if (candidate != null) { result.add(candidate); } } if (logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } return result; }
It is mainly the first for loop, which calls all filters to filter out the configuration classes that do not meet the condition
Usage & Ex amp les
@Conditional
It is an annotation. You can specify a set of conditions. You need to obtain these conditions elsewhere to take effect, such as the previous Condition evaluator and AutoConfigurationImportSelector
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { Class<? extends Condition>[] value(); }
@Conditional is also a meta annotation, which can be used on other annotations. Conditions can also be obtained by using spring's annotation mechanism, such as @ ConditionalOnBean, @ ConditionalOnClass, and their own annotations can be easily extended
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean { ... } @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { ... }
OnBeanCondition
OnBeanCondition is a Condition implementation. The class definition is as follows
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition { }
Combined with the filteringsprinbootcondition abstract class and ConfigurationCondition interface introduced earlier, OnBeanCondition can be used in two ways:
1. As the implementation of ConfigurationCondition, it is used in the ConditionEvaluator class, mainly the following annotations
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean {} @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnSingleCandidate {} @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnMissingBean {}
2. Is the implementation of AutoConfigurationImportFilter, which filters out some configuration classes in the AutoConfigurationImportSelector class
Look separately
As ConfigurationCondition
@Order(Ordered.LOWEST_PRECEDENCE) class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition { @Override public ConfigurationPhase getConfigurationPhase() { return ConfigurationPhase.REGISTER_BEAN; } @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage matchMessage = ConditionMessage.empty(); MergedAnnotations annotations = metadata.getAnnotations(); if (annotations.isPresent(ConditionalOnBean.class)) { Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class); MatchResult matchResult = getMatchingBeans(context, spec); if (!matchResult.isAllMatched()) { String reason = createOnBeanNoMatchReason(matchResult); return ConditionOutcome.noMatch(spec.message().because(reason)); } matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE, matchResult.getNamesOfAllMatches()); } if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations); MatchResult matchResult = getMatchingBeans(context, spec); if (!matchResult.isAllMatched()) { return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll()); } else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(), spec.getStrategy() == SearchStrategy.ALL)) { return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans") .items(Style.QUOTE, matchResult.getNamesOfAllMatches())); } matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE, matchResult.getNamesOfAllMatches()); } if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnMissingBean.class); MatchResult matchResult = getMatchingBeans(context, spec); if (matchResult.isAnyMatched()) { String reason = createOnMissingBeanNoMatchReason(matchResult); return ConditionOutcome.noMatch(spec.message().because(reason)); } matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll(); } return ConditionOutcome.match(matchMessage); } }
1. Annotated with @ Order annotation, it will be sorted together with other conditions in the ConditionEvaluator class
2. Implements the getConfigurationPhase method, which returns REGISTER_BEAN means that this condition will only work when the bean is registered. If this annotation is used on the configuration class, this condition will be ignored when parsing the configuration class
3. Implements the getMatchOutcome method, which handles three annotations @ ConditionalOnBean, @ ConditionalOnSingleCandidate, @ ConditionalOnMissingBean
As AutoConfigurationImportFilter
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition { @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<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean"); outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class); if (outcomes[i] == null) { Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnSingleCandidate"); outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class); } } } return outcomes; } private ConditionOutcome getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation) { List<String> 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; } }
As an AutoConfigurationImportFilter, only @ ConditionalOnBean and @ ConditionalOnSingleCandidate are processed. getOutcomes calls getOutcome through the for loop to process each autoConfigurationClass. The filter method called by getOutcome looks at which beans in requiredBeanTypes do not exist. If they do not exist, noMatch is returned; If there is no Condition that does not exist, null is returned to indicate the current match. You need to continue to look at other conditions
OnClassCondition
@Order(Ordered.HIGHEST_PRECEDENCE) class OnClassCondition extends FilteringSpringBootCondition { }
OnClassCondition implements filteringsprinbootcondition, which has two functions similar to onbean condition. The configuration condition is not implemented in the classoncondition_ CONFIGURATION,REGISTER_BEAN stage will work
Take a look at the implementation of Condition
OnClassCondition will process @ ConditionalOnClass and @ ConditionalOnMissingClass annotations. First, get the class name marked on the annotation through getCandidates. Then, when processing ConditionalOnClass, get the marked but nonexistent class through filter. If it is not empty, the condition is not satisfied; When processing ConditionalOnMissingClass, obtain the marked but existing class through filter. If it is not empty, the condition is not satisfied
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ClassLoader classLoader = context.getClassLoader(); ConditionMessage matchMessage = ConditionMessage.empty(); List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class); if (onClasses != null) { List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader); if (!missing.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class) .didNotFind("required class", "required classes").items(Style.QUOTE, missing)); } matchMessage = matchMessage.andCondition(ConditionalOnClass.class) .found("required class", "required classes") .items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader)); } List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class); if (onMissingClasses != null) { List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader); if (!present.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class) .found("unwanted class", "unwanted classes").items(Style.QUOTE, present)); } matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class) .didNotFind("unwanted class", "unwanted classes") .items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader)); } return ConditionOutcome.match(matchMessage); }
OnExpressionCondition
@Order(Ordered.LOWEST_PRECEDENCE - 20) class OnExpressionCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { String expression = (String) metadata.getAnnotationAttributes(ConditionalOnExpression.class.getName()) .get("value"); expression = wrapIfNecessary(expression); ConditionMessage.Builder messageBuilder = ConditionMessage.forCondition(ConditionalOnExpression.class, "(" + expression + ")"); expression = context.getEnvironment().resolvePlaceholders(expression); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); if (beanFactory != null) { boolean result = evaluateExpression(beanFactory, expression); return new ConditionOutcome(result, messageBuilder.resultedIn(result)); } return ConditionOutcome.noMatch(messageBuilder.because("no BeanFactory available.")); } private Boolean evaluateExpression(ConfigurableListableBeanFactory beanFactory, String expression) { BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver(); if (resolver == null) { resolver = new StandardBeanExpressionResolver(); } BeanExpressionContext expressionContext = new BeanExpressionContext(beanFactory, null); Object result = resolver.evaluate(expression, expressionContext); return (result != null && (boolean) result); } private String wrapIfNecessary(String expression) { if (!expression.startsWith("#{")) { return "#{" + expression + "}"; } return expression; } }
OnExpressionCondition only inherits SpringBootCondition, so there is only one usage. In the implementation, first pass the context getEnvironment(). Resolveplaceholders (expression) replaces the ${} variable in the expression, and then resolves the value of the expression through BeanExpressionResolver (the default is standard BeanExpressionResolver, i.e. SPEL expression), i.e. true/false
OnPropertyCondition
Value and name are one. prefix+name specifies the attribute name, havingValue specifies the value of the attribute, and matchIfMissing is true, indicating that the condition is true when the attribute does not exist
Specific havingValue behavior
Property Value | havingValue="" | havingValue="true" | havingValue="false" | havingValue="foo" |
---|---|---|---|---|
"true" | yes | yes | no | no |
"false" | no | no | yes | no |
"foo" | yes | no | no | yes |
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {}; String prefix() default ""; String[] name() default {}; String havingValue() default ""; boolean matchIfMissing() default false; }
OnPropertyCondition inherits the SpringBootCondition and implements the getMatchOutcome method