Spring Boot @Conditional annotation and conditional implementation

Posted by pmaonline on Sat, 26 Feb 2022 17:49:18 +0100

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 ValuehavingValue=""havingValue="true"havingValue="false"havingValue="foo"
"true"yesyesnono
"false"nonoyesno
"foo"yesnonoyes
@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

Topics: Java Spring Spring Boot