SpringBoot中@ConditionalOnBean实现原理解读

作者:brucelwl 时间:2023-04-25 14:24:54 

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之后被处理)

SpringBoot中@ConditionalOnBean实现原理解读

SpringBoot中@ConditionalOnBean实现原理解读

SpringBoot中@ConditionalOnBean实现原理解读

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

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

SpringBoot中@ConditionalOnBean实现原理解读

SpringBoot中@ConditionalOnBean实现原理解读

解决有两种方式:

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

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

来源:https://blog.csdn.net/u013202238/article/details/89295574

标签:SpringBoot,@ConditionalOnBean,原理
0
投稿

猜你喜欢

  • android点击无效验证的解决方法

    2022-02-28 03:55:08
  • Unity3D网格功能生成球体网格模型

    2021-08-01 01:21:47
  • MyBatis查询时属性名和字段名不一致问题的解决方法

    2023-10-23 16:56:36
  • Spring超详细讲解创建BeanDefinition流程

    2023-11-25 08:37:55
  • C#判断系统是32位还是64位的方法

    2022-04-01 12:44:04
  • android利用剪切板传递数据

    2022-06-24 06:37:40
  • java使用EasyExcel导入导出excel

    2022-10-31 03:48:30
  • java并发编程之深入理解Synchronized的使用

    2023-10-11 09:21:13
  • 详解Android(共享元素)转场动画开发实践

    2021-09-28 21:33:41
  • Java如何重写object类的equals方法详解

    2023-09-01 15:54:57
  • C#开启线程的四种示例

    2022-06-01 13:04:37
  • c#定时器和global实现自动job示例

    2023-10-04 22:30:29
  • java二叉查找树的实现代码

    2022-08-12 02:36:43
  • C#发送HttpPost请求来调用WebService的方法

    2021-06-11 03:36:30
  • 深入理解Java中的弱引用

    2023-07-19 20:14:53
  • 深入浅析SpringBoot中的自动装配

    2021-08-21 17:45:11
  • Java中Set集合的使用和底层原理解析

    2022-12-16 20:54:28
  • c# FTP上传文件实例代码(简易版)

    2023-09-08 11:43:56
  • Android PopupWindow 点击外面取消实现代码

    2022-02-04 20:52:05
  • C#图形区域剪切的实现方法

    2021-09-12 10:15:50
  • asp之家 软件编程 m.aspxhome.com