深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

本文探讨了Spring Boot中@ConditionalOnBean注解失效的原因,特别是在涉及FactoryBean及XML配置场景下的问题定位与解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

最近在使用SpringBoot的@ConditionalOnBean的时候遇到一个很很奇特的问题。即在@Bean中使用@ConditionalOnBean注解,在可以确保需要依赖的Bean一定在此之前已经存在了,但是@ConditionalOnBean仍然未生效。启动的时候报错:not loaded because @ConditionalOnBean (types: ***; SearchStrategy: all) did not find any beans of type ***。接下来我们一起来分析下该问题。

二、问题简介

首先我们进一步介绍下该问题。我们在jar中通过AutoConfiguration引入了RedisClientConfiguration,在其中通过@ConditionalOnBean的方式注解了一个Bean(如下图)。即需要在有Cluster实例存在的情况下才初始化注册RedicClient的是到Spring容器中。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

然后我们在业务代码工程中通过XML的方式在Spring容器中定义了一个Cluster实例,只是这个实例是通过FactoryBean实现的而已。即下图中
ReloadableJimClientFactoryBean是一个FactoryBean,其实际得到的是一个Cluster实例。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

当我们在启动后台会报错,无法加载RedisClient实例,因为Cluster实例并不存在。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

此时问题就出现了,明明在代码工程中我们在xml中定义了Cluster实例,但是为什么启动的过程中SpringBoot在OnCondition判断的却找不不到该实例呢?接下来我们就从源码的层面分析下。

三、问题分析

1、原因分析

首先我们找到ConditionalOnBean的处理源码。其在OnBeanCondition的collectBeanNamesForType中。这里就是SpringBoot通过Cluster类型去查找对应的Bean实例的地方,如果找不到对应的Bean就不会实例化。通过debug咱们可以看到此时却是无法通过
beanFactory.getBeanNamesForType获取到Cluster的Bean实例。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

然后咱们通过源码跟踪到具体获取代码地址:

DefaultListableBeanFactory.getBeanNamesForType

-> DefaultListableBeanFactory.doGetBeanNamesForType

-> AbstractBeanFactory.isTypeMatch

-> AbstractAutowireCapableBeanFactory.getTypeForFactoryBean

然后我们跳转该源码位置,发现通过通过
ReloadableJimClientFactoryBean去获取其实际类型的时候,获取到了一个?,即无法和需要的类型Cluster匹配。于是返回了ResolvableType.NONE,即表示没有找到Cluster类型的Bean。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

那么问题来了,为什么从
ReloadableJimClientFactoryBean的实例去获取具体类型的时候是一个?而并不是其实现的Cluster呢?带着这个问题我们查看ReloadableJimClientFactoryBean的源码。通过如下源码发现其在实现FactoryBean的时候并没有显示指定泛型的类型,到时候Spring不能获取到其实现的类型。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

于是我们尝试重新实现一个
ReloadableJimClientFactoryBean类,让其显示指定对应泛型的类型。代码如下:

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

然后我们在xml中注册该类,并启动工程。启动之后无报错,于是我们再次debug查看关键部分的参数。首先
AbstractAutowireCapableBeanFactory.getTypeForFactoryBean中,其能够真实获取到该FactoryBean对应的类型,且该类型为Cluster。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

然后我们在OnBeanCondition的collectBeanNamesForType中可以看到,能够通过
beanFactory.getBeanNamesForType获取对应的Cluster实例了。因此能够正常实例化RedisClient了。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

2、问题总结

该问题存在的原因还是因为开发的时候没有怎么按规范编写代码导致的。正常情况所有泛型如果已知类型都需要没明确表示。

可以看到如果FactoryBean的类没有指定泛型类型,会导致Spring无法获取到该FactoryBean对应的实际实例的类型。所以导致了ConditionalOnBean无法通过类型找到对应的Bean实例,从而引起ConditionalOnBean注解的Bean实例无法被实例化。

四、延伸问题

在定位该问题的过程中,我遇到了另外一个导致ConditionalOnBean无法生效的问题。我们与如下配置:在业务代码(非jar)中通过@ConditionalOnBean的方式注解了一个Bean(如下图)。即需要在有Cluster实例存在的情况下才初始化注册RedicClient的是到Spring容器中。同时我们在工程中通过xml注入如JimClientFactoryBean类(该类没有上述的FactoryBean泛型类型问题)。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

然后启动代码,发现同样报错,无法正常实例化Person类。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

通过从源码层面分析Configuration的加载顺序(如下图),可以看到在Configuration中,BeanMethod是在XML之前完成加载,因此加载BeanMethod的时候无法读取到xml中定义的实例。源码路径:
ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

于是我们不在xml中定义JimClientFactoryBean,而改在Application类中定义(如下),此时不会有任何错误,工程能够正常启动。

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

这里给大家一个思考问题,如果咱们将代码写成如下方式,那么工程能够正常启动么?

深入源码分析SpringBoot中使用@ConditionalOnBean无效的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值