遇到一个@ConditionalOnMissingBean失效的问题,今天花点时间来分析一下。

现场回放

services

首先介绍下代码结构:有RunService,以及它的两个实现类:TrainRunServiceImplCarRunServiceImpl

RunService

public interface RunService {void run();
}

TrainRunServiceImpl

public class TrainRunServiceImpl implements RunService {@Overridepublic void run() {System.out.println("开火车,wuwuwuwuwu");}
}

CarRunServiceImpl

public class CarRunServiceImpl implements RunService {@Overridepublic void run() {System.out.println("汽车,dididi");}
}

操作类

操作类MyInitBean中,注入了RunServicebyType

@Component
public class MyInitBean implements InitializingBean {@Autowiredprivate RunService runService;@Overridepublic void afterPropertiesSet() throws Exception {runService.run();}
}

configuration

我们在配置类中,注入RunService的实现bean,并通过@ConditionalOnMissingBean来判断是否注入。

@Configuration
public class MyConfiguration {@Bean@ConditionalOnMissingBeanpublic RunService carRunServiceImpl() {return new CarRunServiceImpl();}@Beanpublic RunService trainRunServiceImpl() {return new TrainRunServiceImpl();}
}

抛出异常

按照上述的代码,执行后,本以为会成功执行,但是却抛出了异常,异常信息如下:

在spring容器中存在了两个RunService实现类。
这导致了MyInitBean无法决定它到底该使用这两个中的哪一个。(默认是byType注入的)

按照上述的异常信息,它给出了两种解决方案:

@Qualifier
在注入bean时,指定bean的名称.

@Controller
public class MyInitBean implements InitializingBean {@Autowired@Qualifier("carRunServiceImpl")private RunService runService;
}
  • 通过@Configuration配置类注入的bean,默认名称为方法名称
  @Bean //  `trainRunServiceImpl `public RunService trainRunServiceImpl() {return new TrainRunServiceImpl();}
  • 直接在类头部申明注入的bean,默认名称为类名称
@Service  //  `trainRunServiceImpl`
public class TrainRunServiceImpl implements RunService {}

@Primary
@Primary的作用是,在bean存在多个候选者且无法决定使用哪一个时,优先使用带有该注解的bean.

  • 在配置类中Configuration添加
  @Bean@Primarypublic RunService trainRunServiceImpl() {return new TrainRunServiceImpl();}
  • 在类申明中添加
@Primary
public class TrainRunServiceImpl implements RunService {}

注意
在上述给出的两种方法中,无论是使用@Primary还是这里容器中仍然存在多个实现类,
这并不是我们想要的结果。
这里为什么@ConditionalOnMissingBean会失效呢?

问题定位

在进行问题定位前,我们先来回顾一下@ConditionalOnMissingBean的工作原理

工作原理

@ConditionalOnMissingBean
ConditionalOnMissingBean的注解定义如下:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {Class<?>[] value() default {};String[] type() default {};//略....
}

@ConditionalOnMissingBean通常可以有如下三种使用方式:

    @Bean
//    @ConditionalOnMissingBean(type ="xxx.yyy.zzz.service")
//    @ConditionalOnMissingBean(value = RunService.class)@ConditionalOnMissingBean //无参数,表示按照返回值类型过滤public RunService carRunServiceImpl() {return new CarRunServiceImpl();}

在注解上看到了一个OnBeanCondition类,在@ConditionalOnBean,ConditionalOnSingleCandidateConditionalOnMissingBean都看到了它的身影。

OnBeanCondition

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context,AnnotatedTypeMetadata metadata) {//ConditionalOnBean  略//ConditionalOnSingleCandidate 略if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {//寻找 @ConditionalOnMissingBean 匹配的 type;BeanSearchSpec spec = new BeanSearchSpec(context, metadata,ConditionalOnMissingBean.class);//从容器中寻找指定的type ---  step1MatchResult matchResult = getMatchingBeans(context, spec);if (matchResult.isAnyMatched()) {//如果存在指定的type//reason:  found beans of type 'service.Service' AServiceImplString reason = createOnMissingBeanNoMatchReason(matchResult);//创建 ConditionOutcome.noMatch: return new ConditionOutcome(false, message);return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, spec).because(reason));}matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec).didNotFind("any beans").atAll();}//默认 创建 ConditionOutcome.match : return new ConditionOutcome(true, message);return ConditionOutcome.match(matchMessage);}}

ConditionOutcome 的用法:当match= true时,才注入容器.
@ConditionalOnMissingBean找到了匹配项,则返回ConditionOutcome.notMatch,则不注入容器。

问题出在哪?

有了上面的一系列原理支撑,但是为什么没有执行到我们想要的结果呢?
debug执行后,发现问题出现在OnBeanCondition .getMatchingBeans(context, spec)这个方法中。
首先再次回顾下配置类:

在注入carRunServiceImpl时,执行OnBeanCondition .getMatchingBeans(context, spec)并没有找到下面定义的trainRunServiceImpl.

真相只有一个:
@Configuration 在初始化bean的时候,顺序出现了问题,那么如何控制初始化bean的顺序呢?

解决问题

一顿分析之后,我们发现只要控制了bean的加载顺序之后,上述的问题就可以解决了。
接下来我们来尝试控制bean初始化顺序:

Configuration中bean使用@Order ----------------- failure

@Configuration
public class MyConfiguration {@Order(2)@Bean@ConditionalOnMissingBeanpublic RunService carRunServiceImpl() {return new CarRunServiceImpl();}@Order(1)@Beanpublic RunService trainRunServiceImpl() {return new TrainRunServiceImpl();}
}

Configuration 调整bean申明顺序----------------- success
将带有@ConditionalOnMissingBean注解的bean,申明在代码的末尾位置,操作成功:

@Configuration
public class MyConfiguration {@Beanpublic RunService trainRunServiceImpl() {return new TrainRunServiceImpl();}@Bean@ConditionalOnMissingBeanpublic RunService carRunServiceImpl() {return new CarRunServiceImpl();}
}

配置多个Configuration类,并通过@Order指定顺序---------------- failure

@Configuration
@Order(Ordered.LOWEST_PRECEDENCE) //最低优先级
public class MyConfiguration {@Bean@ConditionalOnMissingBeanpublic RunService carRunServiceImpl() {return new CarRunServiceImpl();}
}@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE) //最高优先级
public class MyConfiguration2 {@Beanpublic RunService trainRunServiceImpl() {return new TrainRunServiceImpl();}
}

@Configuration并不能通过@Order指定顺序。
大胆猜测下@Configuration通过配置类名的自然顺序来加载的。

@Configuration配置类加载顺序通过类名顺序来加载 ------- 验证success
MyConfiguration2重命名为Configuration2,而它的加载顺序在MyConfiguration之前,执行程序成功。

这里貌似所有的问题似乎都解决了, 只需要我们自定义的配置类名称保证最优先加载就可以了。我们只需要注意配置类的命名规则即可.
但是,这种解决方案,似乎并不是那么令人信服。

@AutoConfigureBefore,@AutoConfigureAfter
经查文档,终于找到了需要的东西:我们可以通过@AutoConfigureBefore,@AutoConfigureAfter来控制配置类的加载顺序。

@Configuration
public class MyConfiguration {@Bean@ConditionalOnMissingBeanpublic RunService carRunServiceImpl() {return new CarRunServiceImpl();}
}@Configuration
@AutoConfigureBefore(MyConfiguration.class)
public class MyConfiguration2 {@Beanpublic RunService trainRunServiceImpl() {return new TrainRunServiceImpl();}
}

注意:
如果要开启@EnableAutoConfiguration需要在META-INF/spring.factories文件中添加如下内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xxx.configuration.MyConfiguration2,\
xxx.configuration.MyConfiguration

结论

我们需要控制目标bean的加载顺序即可。
但是我们在实际的使用一些通用plugin过程中(如redis),并没有刻意的指定bean的加载顺序,这是为什么呢?
因为:在实际的应用过程中,我们使用第三方插件,他们的默认配置都会存在于插件的jar包中,而我们的个性化配置则存在于自身的应用中。
而容器会优先执行classes/,然后才执行jars/classes.

ConditionalOnMissingBean失效问题追踪相关推荐

  1. 嵌入式Linux开发系列之一: 走进嵌入式Linux的世界

    走进嵌入式Linux的世界 一.嵌入式系统 嵌入式系统是以应用为中心,以计算机技术为基础,并且软硬件是可裁剪的,适用于对功能.可靠性.成本.体积.功耗等有严格要求的专用计算机系统.嵌入式系统最典型的特 ...

  2. 走进嵌入式Linux的世界

    一.嵌入式系统 嵌入式系统是以应用为中心,以计算机技术为基础,并且软硬件是可裁剪的,适用于对功能.可靠性.成本.体积.功耗等有严格要求的专用计算机系统.嵌入式系统最典型的特点是与人们的日常生活紧密相关 ...

  3. 嵌入式Linux开发(转载)

    导读: 随着信息化技术的发展和数字化产品的普及,以计算机技术.芯片技术和软件技术为核心的嵌入式系统再度成为当前研究和应用的热点,通信.计算机.消费电子技术(3C)合一的趋势正在逐步形成,无所不在的网络 ...

  4. Spring cloud(Finchley)微服务框架,sleuth整合zipkin链路追踪失效的问题

    一.首先说问题: 1.springCloud在使用链路追踪组件sleuth整合zipkin的过程中链路追踪信息切都是正常: 2.微服务太多需要使用组件Config对每个微服务的的配置文件进行统一管理: ...

  5. 解决sleuth链路追踪失效的问题,sleuth版本升级为3.1.3后X-B3-TraceId:-打印不出来了,解决方案

    近期新项目,直接引入spring-cloud-starter-sleuth <!--springCloud链接追踪--><dependency><groupId>o ...

  6. 据我所知,这是第一个完整实现运动分割、动态目标追踪等的「开源」动态SLAM系统!...

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 今天给大家分享一篇最新文章,VDO-SLAM :一种动态目标感知的视觉SLAM系统,原文名称 VDO- ...

  7. 跨镜追踪“智”眼识人技术策略研究及实现

    Labs 导读 目前,在公共场景和个人应用场景中,监控摄像头安装总数已经超过了1.75亿,但大部分均为普通摄像头,通过监控录像实时存储和事件发生后调取查阅的方式被使用,它们缺少自主认知和决策的&quo ...

  8. 仿真环境跟车2分钟,就让自动驾驶系统撞上马路牙子,攻破率超90%,多传感器融合系统都失效...

    鱼羊 萧萧 发自 凹非寺 量子位 报道 | 公众号 QbitAI 自动驾驶领域目前最强的MSF(多传感器融合)定位算法,再次被攻破了. 攻击之下,平均30秒内,正常行驶中的自动驾驶汽车就撞上了马路牙子 ...

  9. QT mouseMoveEvent事件(qmainwindow内追踪鼠标事件事件)

    最近用Qt软件界面,需要用到mouseMoveEvent,研究了下,发现些问题,分享一下. 在Qt中要捕捉鼠标移动事件需要重写MouseMoveEvent,但是MouseMoveEvent为了不太耗资 ...

最新文章

  1. golang net包 ip相关函数 简介
  2. [转载]ASP.NET Core文件上传与下载(多种上传方式)
  3. Matlab:利用Matlab实现布朗运动模拟
  4. HDFS配置Kerberos
  5. 牛客 - 完全图(二分)
  6. MobIM仅为开发者提供即时通讯的消息通道服务
  7. 【深度学习】——DNN后向传播、CNN后向传播文章汇总
  8. uni-app小程序本地打包超过2M不能预览问题;小程序打包过大不能预览和真机调试;uni-app分包;
  9. linux db2 权限管理,DB2五种管理权限
  10. Centos6.8下SVN安装
  11. Linux源代码组织架构
  12. 备份redis服务并ftp上传(shell)
  13. Tomato多拨脚本
  14. css修改输入框的placeholder颜色
  15. {面试题4: 替换空格}
  16. 台式机也应该设置为WIN10节能模式
  17. 【实用技巧】PDF文件去密码和去水印(文件转换网站等推荐)
  18. 四种插头类型:XH、VH、SM、HY
  19. sam格式的结构和意义_SAM格式说明
  20. 微服务拆分之道,几条策略和坚持的原则

热门文章

  1. CCM 摄像模组结构组成部分
  2. Android Studio自带图标制作利器 Image Asset Studio
  3. [人工智能-深度学习-61]:生成对抗网络GAN - 图像融合的基本原理与案例
  4. jQuery之浏览器打印插件
  5. 车用计算机电路板,使用车充、LED头灯电路板制作1.5V电源模块(可代替1号电池)...
  6. 领取CPS外卖红包攻略及小程序项目源码
  7. 西门子博途V15.0安装更新包UPD4时遇到版本不一致的提示问题及处理对策
  8. 计算机制图符号制作,基本流程图的制作-通过在线制图工具绘制
  9. MATLAB | 绘制复指数函数 y = exp(j*w*n)的三维图像
  10. php源码怎样修改logo,dedecms网站改logo怎么操作