In the last section, we talked about how SpringBoot automatic configuration is judged by @Conditional-related annotations. In this section, we will look at the principle of @Conditional-related annotations.
@Conditional use demonstration
Create a new ControllerConditional class that implements the Condition interface and the matches method, returning false
public class ControllerConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false; }}Copy the code
The Controller class to add @ Conditional (ControllerConditional. Class)
@RestController
@Conditional(ControllerConditional.class)
public class Controller {
@RequestMapping("/hello")
public String hello() {return "hello"; }}Copy the code
Try to get the Controller class in the main function.
@SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(Application.class, args); String[] beanNamesForType = context.getBeanNamesForType(Controller.class); System.out.println(Arrays.toString(beanNamesForType)); }}Copy the code
The console will print out an empty array []. Now remove the Controller class @ Conditional (ControllerConditional. Class) annotations, console and can print out (the Controller)
The principle of @Conditional annotations
From the simple example above, it should be clear to use the @Conditional annotation that if the matches method returns false, the class will not be scanned; otherwise it will be scanned into the Spring container. Here’s how they work.
Return to the previous section we speak parse Component, PropertySources, ComponentScan this a few annotations, enter processConfigurationClass method, found a line of code before parsing.
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
Copy the code
ShouldSkip is where the @conditional annotation should be judged (shouldSkip is used elsewhere, but the basic principle is the same, or just the same), so let’s take a look at its parameters and conditionEvaluator. Locate the constructor for the current class and find the following information.
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
...
this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
}
public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
this.context = new ConditionContextImpl(registry, environment, resourceLoader);
}
Copy the code
Constructors are not complex and should not be a problem. Let’s take a look at the two parameters of the shouldSkip method and follow the method back.
this.metadata = new StandardAnnotationMetadata(beanClass, true); public StandardAnnotationMetadata(Class<? > introspectedClass, boolean nestedAnnotationsAsMap) { super(introspectedClass); this.annotations = introspectedClass.getAnnotations(); this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; }Copy the code
Metadata is StandardAnnotationMetadata here, and the second parameter is an enumeration. With that in mind, go to the shouldSkip method.
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
if(metadata == null || ! metadata.isAnnotated(Conditional.class.getName())) {return false; } // Recursively call to ensure that each class is scannedif (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
returnshouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); List<Condition> conditions = new ArrayList<>();for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if(condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } // Check the matches method for each class in turn, one of which returnsfalseSkip this classif((requiredPhase == null || requiredPhase == phase) && ! condition.matches(this.context, metadata)) {return true; }}return false;
}
Copy the code
The logic of the shouldSkip method is not complicated. Get all the argument classes in conditional annotations and call matches in turn. If any method returns false, skip that class. So here we see the arguments and call to the matches method. In this case, there should be no problem with the principle of conditional annotations.
ConditionalOnXXX annotations are derived from conditional annotations as examples.
ConditionalOnClass the principle of annotations
ConditionalOnClass: ConditionalOnClass (value) ConditionalOnClass (String) ConditionalOnClass (name) ConditionalOnClass also includes an @Conditional(onclasscondition.class) annotation. So, actually ConditionalOnClass annotate the judgment of the conditions is OnClassCondition matches method of this class.
@Conditional(OnClassCondition.class) public @interface ConditionalOnClass { Class<? >[] value() default {}; String[] name() default {}; }Copy the code
So there’s nothing left to say, just go into the OnClassCondition class and look for the matches method. Finally, in its parent, SpringBootCondition, it finds the matches method. The code is as follows:
@Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// Get the name of the class or method annotated with @conditionalonClass. String classOrMethodName = getClassOrMethodName(metadata); ConditionOutcome = getMatchOutcome(context, metadata); ConditionOutcome = getMatchOutcome(context, metadata);logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
returnoutcome.isMatch(); }... }Copy the code
As you can see from the code, the key method is in getMatchOutcome, so go into that method.
@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ClassLoader classLoader = context.getClassLoader(); ConditionMessage matchMessage = ConditionMessage.empty(); List<String> onClasses = getCandidates(metadata, conditionalonclass.class); // List<String> onClasses = getCandidates(metadata, conditionalonclass.class);if(onClasses ! = null) {// Filter these classes, 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));
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class"."required classes").items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader)); }...return ConditionOutcome.match(matchMessage);
}
Copy the code
ConditionalOnClass: ConditionalOnClass: ConditionalOnClass: ConditionalOnClass: ConditionalOnClass: ConditionalOnClass: ConditionalOnClass: ConditionalOnClass: ConditionalOnClass: ConditionalOnClass: ConditionalOnClass First look at getCandidates:
private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<? > annotationType) { MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes(annotationType.getName(),true);
if (attributes == null) {
return null;
}
List<String> candidates = new ArrayList<>();
addAll(candidates, attributes.get("value"));
addAll(candidates, attributes.get("name"));
return candidates;
}
Copy the code
ConditionalOnClass name attribute and value attribute are obtained.
Let’s look at the filter method. Before entering the filter method, let’s look at the judgment condition ClassNameFilter.MISSING
MISSING {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return !isPresent(className, 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);
}
Copy the code
The logic is clear: if the class can be loaded, the judgment succeeds, otherwise the judgment fails. Now enter the filter method.
protected 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) {// Determine the criteria we added one by one, and add them to the list if they don't matchif(classNameFilter.matches(candidate, classLoader)) { matches.add(candidate); }}return matches;
}
Copy the code
The filter method is to use the judgment conditions just described to judge, find the inconsistent add to the list and return, and finally generate the result.
Therefore, by now, the principle of relevant annotations of conditional should be clear, and the principle of other derivative classes is also similar, so I will not analyze them one by one.