Spring中@Condition底层实现原理

@Condition是Spring中很关键的一个注解,它能够帮助我们只注册符合条件的Bean,我们可以通过配置、容器中的Bean、Java版本等筛选符合bean注册。

1.注解逻辑

以ConditionOnBean为例

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

本质上引入了OnBeanCondition类来进行筛选,而这个类是继承于Condition类来实现的。

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

}

2.SpringBoot扩展

SpringBoot为了使这个注解能够更加全面,定义了抽象类SpringBootCondition,所有的条件注解都是继承于此类,但SB提供的所有注解都没有实现matches方法,只有在SpringBootCondition中有,那么我们可以得出其实是使用了模版方法

@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.具体的getMatchOutcome方法

@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());
	}
	// 省略其他条件注解判断逻辑
}


定位到getMatchingBeans方法,知道本质就是通过去容器获取该类型的bean集合,如果获取到了说明容器已经有该bean就会封装ConditionOutcome条件结果返回。

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

同理我们可以看其他条件注解 @ConditionalOnClass @ConditionalOnProperty等具体实现也是获取环境配置或者class.forName去判断是否有这个类。

2.调用入口

1.BeanFactoryPostProcessor

condition和BeanFactoryPostProcessor有什么关系呢? 我们都知道@condition注解的作用就是避免注册某些我们不需要的bean。那我们入手就在bean的扫描过程。那么我们定义到自动配置类扫描代码处,即ConfigurationClassPostProcessor类

我们定位到配置类注册逻辑处,postProcessBeanDefinitionRegistry() -> processConfigBeanDefinitions()

可以发现内部有parse类就是用于解析配置。最后跟踪到processConfigBeanDefinitions处会发现关键调用入口

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

最后我们跟踪到ConditionEvaluator就能发现本质就是调用了matches方法进行匹配

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;
}

看到这里会不会有个疑问,明明是按顺序解析Bean信息,如果使用了@ConditionOnBean,但解析该配置类时并未引入相同类型的Bean,解析完再解析其他同类型的Bean,该条件注解不就不起作用了吗?

是的,解析顺序在条件注解判断时候也很重要,像ConditionOnBean判断容器中是否有该bean,但该bean还未注册到容器中,相当于判断就失效了,所有bean的解析顺序也很重要,所以我们会发现一般我们会先注册自己代码中的bean,然后扫描自动配置的bean。而同一配置类内我们可以用@AutoConfigureBefore等注解指定扫描顺序

3.注解扫描顺序

我们查看AutoConfigurationSorter中的getInPriorityOrder方法

  1. 按照字母顺序排序
  2. 按照@AutoConfigureOrder排序
  3. 按照@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;
}

经过上面的排序后就能保证配置类的加载顺序,保证Bean创建流程。

总结

@Condition注解帮助我们选择适当的bean进行注册,而不同的bean注册时候又有错综复杂的依赖关系,在SpringBoot中都帮我们很好解决了。

end
  • 作者:Endwas(联系作者)
  • 发表时间:2022-09-29 23:23
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转博主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者名字和博客地址
  • 评论

    11
    你好