>>>> 😜😜😜 Github: 👉 github.com/black-ant CASE Backup: 👉 gitee.com/antblack/ca…
A. The preface
In this chapter, I will look at the usage and principle of Conditional, and first look at the overall architecture
2. Use
- ConditionalOnBean: when the container contains the specified Bean.
- ConditionalOnMissingBean: when there is no specified Bean in the container.
- @ ConditionalOnSingleCandidate: when specifying the Bean in the container is only one, or even though there are multiple specify preferred Bean
- ConditionalOnClass: when the classpath has a specified class.
- @ ConditionalOnMissingClass: when there is no classpath under the condition of the specified class.
- ConditionalOnProperty: Does the specified attribute have a specified value
- ConditionalOnResource: Does the classpath have a specified value
- ConditionalOnExpression: Based on the SpEL expression as the judgment condition.
- ConditionalOnJava: ConditionalOnJava version
- ConditionalOnJndi: difference at the specified location in the presence of JNDI
- @ ConditionalOnNotWebApplication: under the condition of the current project is not the Web project
- @ ConditionalOnWebApplication: under the condition of the current project is Web purpose
2.1 Basic Usage
2.2 Custom Conditionality
public class DefaultConditional implements Condition {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
logger.info("------> Make Conditional judgment <-------");
return false; }}Copy the code
2.3 Basic Use
@Bean
@ConditionalOnBean(TestService.class)
public ConfigBean getConfigBean(a) {
logger.info("------> ConditionalOnBean <-------");
return new ConfigBean();
}
Copy the code
3. Analysis of the principle of customization
3.1 Conditional entry
For the Configuration. @ the Bean’s creation method, is the starting point of Conditinal refush# invokeBeanFactoryPostProcessors, In which invokes the ConfigurationClassPostProcessor for processing
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
// Step 1: Obtain the metadata information of @bean in the current Configuration
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
// Determine whether the current Bean should be skipped
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return; }}Copy the code
3.2 The process of Conditional judgment
ConditionEvaluator is the core handler class that eventually calls shouldSkip to decide whether to skip
// C- ConditionEvaluator
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// Step 1: Return if the current Bean does not contain @Conditional
if (metadata == null| |! metadata.isAnnotated(Conditional.class.getName())) {return false;
}
// Step 2: There are two types of ConfigurationPhase, representing two configuration phases
// PARSE_CONFIGURATION: The Condition should be evaluated when the @configuration class is parsed. If the Condition does not match, the @Configuration class will not be added
// REGISTER_BEAN: The condition does not prevent @Configuration classes from being added. All @Configuration classes will be resolved when the condition is evaluated
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
// Step 3: Get all Condition objects
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); }}// Step 4: Sort
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// Step 5: Final Condition matching process
if ((requiredPhase == null|| requiredPhase == phase) && ! condition.matches(this.context, metadata)) {
return true; }}return false;
}
Copy the code
All the way up to here you start matching methods
4. Conventional loading mode
parsing
Known Conditional are implemented based on SpringBootCondition, its specific abstract class is FilteringSpringBootCondition, look at the main inheritance relationships
Remove classes that do not need to be configured
The first step is to quickly remove unnecessary classes. The main process is as follows:
- Starting point: AbstractApplicationContext # # invokeBeanFactoryPostProcessors refresh
- Processing: ConfigurationClassPostProcessor # postProcessBeanDefinitionRegistry main logic processing
- ConfigurationClassFilter # filter
- Matching: FilteringSpringBootCondition # match
Note that a match is being made to get an AutoConfigurationEntry based on AnnotationMetadata of the Configuration class being imported
4.1 the Filter interceptor
Filter interception is in ConfigurationClassFilter, where all Conditional interceptions are processed
private static class ConfigurationClassFilter {
// Automatically configured metadata
private final AutoConfigurationMetadata autoConfigurationMetadata;
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
// Here are all Confiturations classes
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
/ / OnBeanCondition included here, OnClassCondition, OnWebApplicationCondition 3 kinds
for (AutoConfigurationImportFilter filter : this.filters) {
// Gets whether a match match exists
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;
}
// If it cannot be skipped, record the current Confiturations class
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if(candidate ! =null) { result.add(candidate); }}returnresult; }}Copy the code
4.2 FilteringSpringBootCondition match in the match
Here we override the match method of its parent class
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
// Step 1: Prepare a Report object for recording
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
// Step 2: Get all the corresponding Condition methods
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
// Assign the array in match to true when outcomes matches ConditionOutcome. Otherwise, return false
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if(! match[i] && outcomes[i] ! =null) {
// Log
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if(report ! =null) {
/ / # like ConditionEvaluationReport SortedMap deposit evaluation criteria
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]); }}}return match;
}
Copy the code
The role of ConditionEvaluationReport
This object records detailed information and log information about condition matching during automatic configuration
public final class ConditionEvaluationReport {
/ / the name of the bean
private static final String BEAN_NAME = "autoConfigurationReport";
// Create a parent conditional match object
private static final AncestorsMatchedCondition ANCESTOR_CONDITION = new AncestorsMatchedCondition();
// Store the class name or method name (key), condition evaluation output object (value)
private final SortedMap<String, ConditionAndOutcomes> outcomes = new TreeMap<>();
// Is the original conditional match object
private boolean addedAncestorOutcomes;
// The parent's condition evaluation report object
private ConditionEvaluationReport parent;
// Record the names of classes that have been excluded from the condition evaluation
private final List<String> exclusions = new ArrayList<>();
// Record the name of the candidate class for the conditional evaluation
private final Set<String> unconditionalClasses = new HashSet<>();
}
Copy the code
4.3 getOutcomes gets
Here, OnBean is taken as an example, where there is a certain correlation:
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// Step 1: Initialize an array the size of the processing class
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
// Step 2: Iterate through all the autoConfigurationClasses
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if(autoConfigurationClass ! =null) {
Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
/ / determine whether exist ConditionalOnBean labeling method
outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
// Decide whether ConditionOutcome should be output
if (outcomes[i] == null) {
Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
"ConditionalOnSingleCandidate");
// Return whether to processoutcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class); }}}return outcomes;
}
Copy the code
4.4 How to determine whether the evaluation criteria are met
Note the matches and FilteringSpringBootCondition isn’t one
- FilteringSpringBootCondition # match: based on the AutoConfigurationImportFilter, automatic configuration of a given class candidate application filter
- SpringBootCondition # matches: The final judgment needs to be returned
Calling process
- In loadBeanDefinitionsForBeanMethod and similar process invocation shouldSkip to jump to the logic
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Get the method name or class name of the annotation
String classOrMethodName = getClassOrMethodName(metadata);
try {
Metadata instanceof (ClassMetadata instanceof)
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// Very simple print log, Trace level
logOutcome(classOrMethodName, outcome);
/ / record results, through ConditionEvaluationReport and recordEvaluation method implementation
recordEvaluation(context, classOrMethodName, outcome);
// Returns whether the match was successful
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw newIllegalStateException(......) ; }catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on "+ getName(metadata), ex); }}Copy the code
The whole point here is to call getMatchOutcome and say yes or no, and getMatchOutcome needs to be subclassed
5. GetMatchOutcome Details
5.1 OnClass
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Step 1: Get the current container ClassLoader
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
/ / Step 2: determine whether have ConditionalOnClass constraints
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if(onClasses ! =null) {
// Step 2-1: filter to check whether classes are missing
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));
}
// Step 2-2: Build matchMessage
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class"."required classes")
.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}
// Step 3: Similarly, determine whether MissClasses are needed
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if(onMissingClasses ! =null) {
/ /... ConditionalOnClass
}
return ConditionOutcome.match(matchMessage);
}
Copy the code
ConditionMessage
5.2 OnBean
Similar to onBeans, one is shown here
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
MergedAnnotations annotations = metadata.getAnnotations();
if (annotations.isPresent(ConditionalOnBean.class)) {
// Get ConditionalOnBean annotations
Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
// Step 2: Return the matching result
/ / its internal by getNamesOfBeansIgnoredByType getBeanNamesForType methods such as to determine whether a 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())) {
/ /...
}
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
/ /...
}
return ConditionOutcome.match(matchMessage);
}
Copy the code
conclusion
Conditionality itself is not difficult, this part is mainly to prepare for perfecting the atlas and the subsequent starter start process scheme.
There are a few links in the whole process to understand:
- Conditional in Spring inherits SpringBootCondition and implements its getOutcomes method
- GetOutcomes is used to quickly remove Configuration that does not need to be loaded, and getMatchOutcome is used to verify matching relationships
- ShouldSkip in ConditionEvaluator is usually used to determine whether the @bean process needs to be skipped