SpringBoot中@ConditionalOnBean实现原理解读

 更新时间:2023年02月17日 09:23:03   作者:brucelwl  
这篇文章主要介绍了SpringBoot中@ConditionalOnBean实现原理,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

SpringBoot @ConditionalOnBean实现原理

在SpringBoot1.5.X时

判断条件是OR,SpringBoot2.x变为了AND

在spring 启动过程中,会调用BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry后置处理器 调用ComponentScanAnnotationParser的parse(..)方法解析配置类,实现类路径下的类文件扫描,

遍历被扫描到的类,并通过@ComponentScan注解上指定的规则进行匹配,默认情况下,SpringBoot扫描@Component标记的类,( 但是在spring.factories中通过key org.springframework.boot.autoconfigure.EnableAutoConfiguration指定配置不会被扫描. 这些配置类会由@EnableAutoConfiguration导入的AutoConfigurationImportSelector来处理);

如果出现匹配的类还会通过ConditionEvaluator.shouldSkip(...)来判断是否忽略这个类,这时候会过滤掉小部分无效类,但是不会在这个期间应用@ConditionalOnBean去验证,

因为其验证实现类OnBeanCondition实现了接口ConfigurationCondition的getConfigurationPhase()方法,该方法返回值是一个枚举值,用于判断什么时期应用这个条件验证, shouldSkip()方法会判断这个接口返回值,OnBeanCondition实现的getConfigurationPhase()返回的是ConfigurationPhase.REGISTER_BEAN,而类扫描期间只会执行没有实现ConfigurationCondition接口,或返回值为null,或为ConfigurationPhase.PARSE_CONFIGURATION的验证器.

所以这个期间不会应用@ConditionalOnBean去验证. 这也可以理解因为这个时候有很多类没有被遍历到,现在执行OnBeanCondition验证肯定多数是false.

这也导致很多最终不会被实例化为Spring bean的类被包装成BeanDefinition(实现类ScannedGenericBeanDefinition ),并注册在DefaultListableBeanFactory 的成员beanDefinitionMap中,key为Bean的名称,同时Bean的名称会保存在List集合beanDefinitionNames中, 对于一些手动注册的Bean的名称会保存在List集合manualSingletonNames中,

然后对于parse(..)返回的BeanDefinition集合会遍历调用ConfigurationClassParser的parse(...)递归调用, 当解析到配置中有@Bean定义的Bean,并有条件注解时,会调用ConditionEvaluator.shouldSkip(...)方法,最终调用到的是 Condition实现类的matches(...)方法.

而@ConditionalOnBean注解中的验证实现类OnBeanCondition,最终判断是否有指定的bean就是通过DefaultListableBeanFactory 中的beanDefinitionMap和manualSingletonNames是否包含指定的Bean的名称来验证的.

当然对于扫描到的无效类,在ConfigurationClassPostProcessor的processConfigBeanDefinitions(...)调用parse()方法后会调用this.reader.loadBeanDefinitions(configClasses),来判断最终有效的BeanDefinition,这时候就会执行OnBeanCondition, 移除beanDefinitionMap中无效的BeanDefinition. 

鉴于类路径扫描规则和OnBeanCondition验证实现原理

就可能导致无效问题

例如如下代码:

C希望不存在A Bean时创建,B希望在C bean存在时创建,示例中A配置了Bean,这时B C都不应该创建,但执行结果是C确实没有创建,而B却被创建了.

原因就在于类扫描时,C被扫描了,导致beanDefinitionMap存在C 的BeanDefinition.

但是C在this.reader.loadBeanDefinitions(configClasses),中由于条件验证不通过没有创建被移除.(当然和类扫描顺序也有关系,如果B能够在C之后被扫描到,就会执行正常,所以Spring官网建议通过spring.factories来配置,指定执行顺序或者在能够确定执行顺序的情况下使用@ConditionalOnBean,但是一定不能通过文件名来控制顺序)

注意点

如果将示例中的类配置到spring.factories中即时不指定顺序,也能正常执行,不会创建B C,这是由于spring.factories配置文件中的类并不是全部包装成BeanDefinition然后判断是否有效的,而是排序后封装成ConfigurationClass通过遍历的方式,同样是在this.reader.loadBeanDefinitions(configClasses)方法中判断该类是否通过条件验证是否有效.

当执行到B是,C还没有被处理,所以导致B也不会被创建,(这有点属于巧合导致的正确结果,所以为了安全还是要执行顺序,让B在C之后被处理)

第二种情况, 在配置类中定义Bean,如果使用@ConditionalOnBean依赖的也是配置类中Bean,则执行结果不可控,和配置类加载顺序有关。

示例代码如下:代码中是希望Bean3被创建,但是实际上并没有!!!

解决有两种方式:

1 可以考虑使用@DependsOn来解决,参看Spring中@DependsOn注解的作用及实现原理。

2 如果一定要区分两个配置类的先后顺序,可以将这两个类交与EnableAutoConfiguration管理和触发。 也就是定义在META-INF\spring.factories中声明是配置类, 然后通过@AutoConfigureBefore、AutoConfigureAfter、AutoConfigureOrder控制先后顺序。 因为这三个注解只对自动配置类生效。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java中常用解析工具jackson及fastjson的使用

    Java中常用解析工具jackson及fastjson的使用

    今天给大家带来的是关于Java解析工具的相关知识,文章围绕着jackson及fastjson的使用展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • SpringMVC中的SimpleUrlHandlerMapping用法详解

    SpringMVC中的SimpleUrlHandlerMapping用法详解

    这篇文章主要介绍了SpringMVC中的SimpleUrlHandlerMapping用法详解,SimpleUrlHandlerMapping是Spring MVC中适用性最强的Handler Mapping类,允许明确指定URL模式和Handler的映射关系,有两种方式声明SimpleUrlHandlerMapping,需要的朋友可以参考下
    2023-10-10
  • SpringBoot中获取微信用户信息的方法

    SpringBoot中获取微信用户信息的方法

    这篇文章主要介绍了SpringBoot中获取微信用户信息的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • nacos服务注册服务发现依赖配置详解

    nacos服务注册服务发现依赖配置详解

    这篇文章主要为大家介绍了nacos服务注册服务发现依赖配置详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Java执行cmd命令两种实现方法解析

    Java执行cmd命令两种实现方法解析

    这篇文章主要介绍了Java执行cmd命令两种实现方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • JavaGUI实现随机单词答题游戏

    JavaGUI实现随机单词答题游戏

    这篇文章主要为大家详细介绍了JavaGUI实现随机单词答题游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • Java数据结构之循环队列简单定义与用法示例

    Java数据结构之循环队列简单定义与用法示例

    这篇文章主要介绍了Java数据结构之循环队列简单定义与用法,简要描述了循环队列的概念、原理,并结合实例形式分析了java循环队列的定义与使用方法,需要的朋友可以参考下
    2017-10-10
  • java后端解决跨域的几种问题解决

    java后端解决跨域的几种问题解决

    这篇文章主要介绍了java后端解决跨域的几种问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • java获取服务器基本信息的方法

    java获取服务器基本信息的方法

    这篇文章主要介绍了java获取服务器基本信息的方法,涉及java获取系统CPU、内存及操作系统等相关信息的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • 浅谈抛出异常和捕获异常的一些区别

    浅谈抛出异常和捕获异常的一些区别

    这篇文章主要介绍了抛出异常和捕获异常的一些区别,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06

最新评论