【Spring Boot 源码学习】OnBeanCondition 详解
最佳答案 问答题库608位专家为你答疑解惑
Spring Boot 源码学习系列
OnBeanCondition 详解
- 引言
- 往期内容
- 主要内容
- 1. getOutcomes 方法
- 2. getMatchOutcome 方法
- 2.1 ConditionalOnBean 注解处理
- 2.2 ConditionalOnSingleCandidate 注解处理
- 2.3 ConditionalOnMissingBean 注解处理
- 3. getMatchingBeans 方法
- 总结
引言
上篇博文带大家从 Spring Boot
源码深入详解了 OnClassCondition,那本篇也同样从源码入手,带大家深入了解 OnBeanCondition 的过滤匹配实现。
往期内容
在开始本篇的内容介绍之前,我们先来看看往期的系列文章【有需要的朋友,欢迎关注系列专栏】:
Spring Boot 源码学习 Spring Boot 项目介绍 Spring Boot 核心运行原理介绍 【Spring Boot 源码学习】@EnableAutoConfiguration 注解 【Spring Boot 源码学习】@SpringBootApplication 注解 【Spring Boot 源码学习】走近 AutoConfigurationImportSelector 【Spring Boot 源码学习】自动装配流程源码解析(上) 【Spring Boot 源码学习】自动装配流程源码解析(下) 【Spring Boot 源码学习】深入 FilteringSpringBootCondition 【Spring Boot 源码学习】OnClassCondition 详解主要内容
话不多说,马上进入正题,我们开始本篇的内容,重点详解 OnBeanCondition
的实现。
1. getOutcomes 方法
OnBeanCondition
同样也是 FilteringSpringBootCondition
的子类,我们依旧是从 getOutcomes
方法源码来分析【Spring Boot 2.7.9】:
@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {// ...@Overrideprotected 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;}// ...
}
上述 getOutcomes
方法中针对 自动配置数据的循环处理逻辑,大致可总结为如下两种:
-
通过调用
AutoConfigurationMetadata
接口的get(String className, String key)
方法来获取与autoConfigurationClass
关联的名为"ConditionalOnBean"
的条件属性值,可能含多个,存入Set
集合onBeanTypes
变量中;接着调用getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation)
方法来获取过滤匹配结果,并赋值给outcomes[i]
。我们以 RedisCacheConfiguration 为例,可以看到如下配置:
-
如果上述过滤匹配结果
outcomes[i]
为null
,则通过调用AutoConfigurationMetadata
接口的get(String className, String key)
方法来获取与autoConfigurationClass
关联的名为"ConditionalOnSingleCandidate"
的条件属性值,可能含多个,存入Set
集合onSingleCandidateTypes
变量中;接着调用getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation)
方法来获取过滤匹配结果,并赋值给outcomes[i]
。我们以 MongoDatabaseFactoryConfiguration 为例,可以看到如下配置:
有关
AutoConfigurationMetadata
接口的get(String className, String key)
方法的逻辑,请查看 Huazie 的 上一篇博文【Spring Boot 源码学习】OnClassCondition 详解,这里不再赘述。
下面我们继续查看 getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation)
方法的逻辑:
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;
}
进入 getOutcome
方法,可以看到:
- 首先调用父类
FilteringSpringBootCondition
中的filter
方法,来获取给定的类集合requiredBeanTypes
中加载失败的类集合missing
【即当前类加载器中不存在的类集合】; - 如果
missing
不为空,说明存在加载失败的类,则返回 不满足过滤匹配的结果【即 ConditionOutcome.noMatch,其中没有找到missing
中需要的类型】; - 如果
missing
为空,直接返回 null 即可。
2. getMatchOutcome 方法
同 OnClassCondition
一样,OnBeanCondition
同样实现了 FilteringSpringBootCondition
的父类 SpringBootCondition
中的抽象方法 getMatchOutcome
方法。
有关
SpringBootCondition
的介绍,这里不赘述了,请查看笔者的 【Spring Boot 源码学习】OnClassCondition 详解。
通过查看 getMatchOutcome
方法源码,可以看到针对 ConditionalOnBean
注解、ConditionalOnSingleCandidate
注解 和 ConditionalOnMissingBean
注解的三块处理逻辑,下面来一一讲解:
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage matchMessage = ConditionMessage.empty();MergedAnnotations annotations = metadata.getAnnotations();// ConditionalOnBean 注解处理// ConditionalOnSingleCandidate 注解处理// ConditionalOnMissingBean 注解处理return ConditionOutcome.match(matchMessage);
}
2.1 ConditionalOnBean 注解处理
我们来看看 ConditionalOnBean
注解处理逻辑的源码:
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());}
针对上述代码,且听分析如下:
- 首先调用
MergedAnnotations
接口的isPresent(Class<A> annotationType)
方法判断指定的注解类型是直接存在或者元存在【这里相当于调用get(annotationType).isPresent()
】,如果返回 true,表示存在指定的注解类型。 - 如果存在
@ConditionalOnBean
,则- 创建一个条件规范
Spec
对象,该类是从底层的注解中提取的搜索规范; - 接着,调用
getMatchingBeans
方法,并从上下文【context
】中获取与条件规范【spec
】匹配的 Spring Beans 的结果【MatchResult
】; - 然后,检查匹配结果,如果不是所有的条件都匹配,则继续如下:
- 调用
createOnBeanNoMatchReason
方法,创建一个描述条件不匹配原因的字符串并返回; - 返回一个表示未匹配条件的
ConditionOutcome
对象【其中包含了条件规范的消息以及不匹配的原因】;
- 调用
- 否则,更新匹配消息,并记录 找到了所有匹配的 Spring Beans。
- 创建一个条件规范
2.2 ConditionalOnSingleCandidate 注解处理
我们继续查看 ConditionalOnSingleCandidate
注解处理逻辑的源码:
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());}Set<String> allBeans = matchResult.getNamesOfAllMatches();if (allBeans.size() == 1) {matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);}else {List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,spec.getStrategy() == SearchStrategy.ALL);if (primaryBeans.isEmpty()) {return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));}if (primaryBeans.size() > 1) {return ConditionOutcome.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));}matchMessage = spec.message(matchMessage).found("a single primary bean '" + primaryBeans.get(0) + "' from beans").items(Style.QUOTE, allBeans);}}
同样针对上述代码,跟着 Huazie 来一步步分析下:
- 首先调用
AnnotatedTypeMetadata
接口的isAnnotated(String annotationName)
方法判断元数据中是否存在指定注解。如果返回true
,表示元数据中存在指定注解。 - 如果元数据中存在
@ConditionalOnSingleCandidate
注解,则- 创建了一个
SingleCandidateSpec
的对象spec
,并传入上下文 【context
】、元数据 【metadata
】 和注解信息 【annotations
】 ,该类是专门针对@ConditionalOnSingleCandidate
注解的条件规范。 - 接着调用
getMatchingBeans
方法对context
中的所有bean
进行匹配,并将与条件规范【spec
】匹配的 Spring Beans 的结果存储在matchResult
变量中; - 如果没有匹配的
bean
,则返回表示未匹配条件的ConditionOutcome
对象【其中记录了 没有找到任何bean
的信息】; - 否则,获取匹配的所有
bean
名称并存储在allBeans
变量中。- 如果仅有一个匹配的
bean
,则更新匹配消息,并记录找到了 单个bean
的信息; - 否则,获取首选
bean
名称列表,并检查列表是否为空;- 如果列表为空,则返回表示未匹配条件的
ConditionOutcome
对象【其中记录了 一个首选bean
也没有找到 的信息】; - 如果首选
bean
名称列表包含多个bean
,则返回表示未匹配条件的ConditionOutcome
对象【其中记录了 找到了多个首选bean
的信息】; - 否则,更新匹配消息,并记录 找到了首选
bean
的信息。
- 如果列表为空,则返回表示未匹配条件的
- 如果仅有一个匹配的
- 创建了一个
2.3 ConditionalOnMissingBean 注解处理
我们继续查看 ConditionalOnMissingBean
注解处理逻辑的源码:
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();}
经过上述两种处理逻辑的分析,相信大家应该可以看懂第三种处理逻辑的分析:
- 首先调用
AnnotatedTypeMetadata
接口的isAnnotated(String annotationName)
方法判断元数据中是否存在指定注解。如果返回true
,表示元数据中存在指定注解。 - 如果存在
@ConditionalOnMissingBean
注解,则- 创建一个条件规范
Spec
对象,该类是从底层的注解中提取的搜索规范; - 接着,调用
getMatchingBeans
方法,并从上下文【context
】中获取与条件规范【spec
】匹配的 Spring Beans 的结果【MatchResult
】; - 如果存在任何一个匹配的
bean
,则- 调用
createOnMissingBeanNoMatchReason
方法,创建一个描述条件不匹配原因的字符串并返回; - 返回一个表示未匹配条件的
ConditionOutcome
对象【其中包含了条件规范的消息以及不匹配的原因】;
- 调用
- 否则,更新匹配消息,并记录 找不到指定类型的
bean
的信息。
- 创建一个条件规范
3. getMatchingBeans 方法
上述三种注解处理逻辑中,我们都看到了调用 getMatchingBeans
方法,下面重点来讲解一下:
protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {// ...
}
我们可以看到 getMatchingBeans
方法,有两个参数,它们分别是 上下文 【context
】和 条件规范【spec
】;
继续看 getMatchingBeans
方法内部逻辑:
ClassLoader classLoader = context.getClassLoader();ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
这里从上下文【context
】中获取 ClassLoader
和 ConfigurableListableBeanFactory
;
知识拓展:
ClassLoader
是 Java 中的一个接口,用于加载类。它是 Java 类加载机制的核心部分,负责将 .class 文件转换为 Java 类实例。ClassLoader
可以从不同的来源(如文件系统、网络、数据库等)加载类,也可以实现自定义的类加载逻辑。ConfigurableListableBeanFactory
是 Spring 框架中的一个核心接口,它扩展了ListableBeanFactory
接口,提供了更多的配置和扩展功能。它是一个bean
工厂的抽象概念,用于管理 Spring 容器中的bean
对象。ConfigurableListableBeanFactory
提供了添加、移除、注册和查找bean
的方法,以及设置和获取bean
属性值的功能。它还支持bean
的后处理和事件传播。
boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
这里根据 Spec
对象的 SearchStrategy
属性来确定是否考虑 bean
的层次结构。如果SearchStrategy
是 CURRENT
【】,则不考虑层次结构【即 considerHierarchy 为 false
】;否则,考虑层次结构【即 considerHierarchy 为 true
】。
Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
这里获取 Spec
对象的 parameterizedContainers
属性,这是一个包含参数化容器类型的集合
if (spec.getStrategy() == SearchStrategy.ANCESTORS) {BeanFactory parent = beanFactory.getParentBeanFactory();Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,"Unable to use SearchStrategy.ANCESTORS");beanFactory = (ConfigurableListableBeanFactory) parent;}
如果 Spec
对象的 SearchStrategy
属性是 SearchStrategy.ANCESTORS
,则调用 getParentBeanFactory
方法获取其父工厂,并将其转换为 ConfigurableListableBeanFactory
类型。
MatchResult result = new MatchResult();
新建一个 MatchResult
对象,用于存储匹配结果;
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,spec.getIgnoredTypes(), parameterizedContainers);
调用 getNamesOfBeansIgnoredByType
方法,获取被忽略类型的 bean
名称集合 beansIgnoredByType
;
for (String type : spec.getTypes()) {Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,parameterizedContainers);Iterator<String> iterator = typeMatches.iterator();while (iterator.hasNext()) {String match = iterator.next();if (beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match)) {iterator.remove();}}if (typeMatches.isEmpty()) {result.recordUnmatchedType(type);}else {result.recordMatchedType(type, typeMatches);}}
遍历 Spec
对象的 types
属性,它是一个 Set<String>
集合
- 首先,针对每个类型
type
,调用getBeanNamesForType
方法获取匹配的bean
名称集合typeMatches
。 - 然后,使用迭代器遍历这个集合,如果集合中的某个元素在被忽略类型的集合中,就将其从迭代器中移除。
- 最后,如果
typeMatches
集合为空,则记录未匹配的类型;否则,记录匹配的类型。
for (String annotation : spec.getAnnotations()) {Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,considerHierarchy);annotationMatches.removeAll(beansIgnoredByType);if (annotationMatches.isEmpty()) {result.recordUnmatchedAnnotation(annotation);}else {result.recordMatchedAnnotation(annotation, annotationMatches);}}
遍历 Spec
对象的 annotations
属性:
- 首先,针对每个注解
annotation
,调用getBeanNamesForAnnotation
方法获取匹配的bean
名称集合annotationMatches
。 - 然后,从
annotationMatches
集合中移除被忽略类型的集合。 - 最后,如果
annotationMatches
集合为空,则记录未匹配的注解;否则,记录匹配的注解。
for (String beanName : spec.getNames()) {if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {result.recordMatchedName(beanName);}else {result.recordUnmatchedName(beanName);}}
遍历 Spec
对象的 names
属性,对于每个 bean
名称,如果它不在被忽略类型的集合中,并且它在 bean
工厂中存在,就记录匹配的名称;否则,记录未匹配的名称。
总结
本篇 Huazie 带大家介绍了自动配置过滤匹配子类 OnBeanCondition
,内容较多,感谢大家的支持;笔者接下来的博文还将详解 OnWebApplicationCondition
的实现,敬请期待!!!
99%的人还看了
相似问题
猜你感兴趣
版权申明
本文"【Spring Boot 源码学习】OnBeanCondition 详解":http://eshow365.cn/6-10963-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!
- 上一篇: Direct3D融合技术
- 下一篇: [k8s] 常见yml配置和详细解释