The underlying implementation principle of @Condition in Spring

@Condition is a very critical annotation in Spring. It can help us register only beans that meet the conditions. We can filter the registration of qualified beans through configuration, beans in the container, Java version, etc.

1. Annotation logic

Take ConditionOnBean as an example

@Target({
    
     ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
    
    
}

Essentially, the OnBeanCondition class is introduced for filtering, and this class is implemented by inheriting from the Condition class.

@FunctionalInterface
public interface Condition {
    
    
	/**
	*	该方法用于判断该注解条件是否符合,符合返回true
	**/
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

2.SpringBoot extension

In order to make this annotation more comprehensive, SpringBoot defines the abstract class SpringBootCondition. All conditional annotations are inherited from this class. However, all annotations provided by SB do not implement the matches method. They are only available in SpringBootCondition. Then we can conclude that in fact The template method is used .

@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
	String classOrMethodName = getClassOrMethodName(metadata);
	try {
    
    
		// 本质通过子类getMatchOutcome来实现具体方法逻辑
		ConditionOutcome outcome = getMatchOutcome(context, metadata);
		logOutcome(classOrMethodName, outcome);
		recordEvaluation(context, classOrMethodName, outcome);
		return outcome.isMatch();
	}
	catch (NoClassDefFoundError ex) {
    
    
		xxx
	}
	catch (RuntimeException ex) {
    
    
		xxx
	}
}

3. Specific getMatchOutcome method

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
	ConditionMessage matchMessage = ConditionMessage.empty();
	MergedAnnotations annotations = metadata.getAnnotations();
	// 如果是ConditionOnBean则会执行以下判断逻辑
	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());
	}
	// 省略其他条件注解判断逻辑
}


Locate the getMatchingBeans method, and know that the essence is to obtain the beandefinition name of this type by going to the container. Subsequently, the obtained names will be filtered to filter out the conditions that are met.

// 核心逻辑
Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type, parameterizedContainers);

In the same way, we can look at other conditional annotations
@ConditionalOnClass
@ConditionalOnProperty and other specific implementations to obtain the environment configuration or class.forName to determine whether there is such a class.

2. Call the entrance

1.BeanFactoryPostProcessor

What is the relationship between condition and BeanFactoryPostProcessor?
We all know that the function of the @condition annotation is to avoid registering certain beans that we don't need. Then we start with the bean scanning process. Then we define the automatic configuration class scanning code, that is, the ConfigurationClassPostProcessor class

We locate the configuration class registration logic, postProcessBeanDefinitionRegistry() -> processConfigBeanDefinitions()

You can find that there is a parse class inside which is used to parse configuration. Finally, when tracing to processConfigBeanDefinitions, you will find the key call entry.

if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    
    
		return;
}

Finally, when we track the ConditionEvaluator, we can find that the essence is to call the matches method for matching.

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    
    
	if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
    
    
		return false;
	}

	if (phase == null) {
    
    
		if (metadata instanceof AnnotationMetadata &&
				ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
    
    
			return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
		}
		return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
	}
	// 解析注解condition
	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();
		}
		// matches方法进行判断
		if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
    
    
			return true;
		}
	}

	return false;
}

Do you have any questions when you see this? It is obvious that the Bean information is parsed in order. If @ConditionOnBean is used, but the same type of Bean is not introduced when parsing the configuration class, other beans of the same type will be parsed after parsing. This condition Wouldn't the annotation work?

Yes, the parsing order is also important when judging conditional annotations. For example, ConditionOnBean judges whether the bean exists in the container, but the bean has not yet been registered in the container, which means the judgment is invalid. The parsing order of all beans is also very important. So we will find that generally we will first register the beans in our own code, and then scan the automatically configured beans. Within the same configuration class, we can use @AutoConfigureBefore and other annotations to specify the scanning order.

3. Annotate the scanning order

Let’s look at the getInPriorityOrder method in AutoConfigurationSorter

  1. Sort alphabetically
  2. Sort by @AutoConfigureOrder
  3. Sort by @AutoConfigureBefore @AutoConfigureAfter
List<String> getInPriorityOrder(Collection<String> classNames) {
    
    
	AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
			this.autoConfigurationMetadata, classNames);
	List<String> orderedClassNames = new ArrayList<>(classNames);
	// 按字母顺序排序
	Collections.sort(orderedClassNames);
	// 然后按照order顺序排序
	orderedClassNames.sort((o1, o2) -> {
    
    
		int i1 = classes.get(o1).getOrder();
		int i2 = classes.get(o2).getOrder();
		return Integer.compare(i1, i2);
	});
	// 最后按照 @AutoConfigureBefore @AutoConfigureAfter排序
	orderedClassNames = sortByAnnotation(classes, orderedClassNames);
	return orderedClassNames;
}

After the above sorting, the loading order of configuration classes and the Bean creation process can be guaranteed.

Summarize

The @Condition annotation helps us select the appropriate bean for registration, and different beans have intricate dependencies when registering, which is solved for us in SpringBoot.

Guess you like

Origin blog.csdn.net/qq_40922616/article/details/126824751