前几天的下午,阿布正在静静地写着代码,突然,旁边的同事大呼小叫了起来,“阿布,你 sdk 注册的 bean 报错啦,dev 环境我应用发不起来了”。我扒过去一看,哎呀,这不就是注册了重复的 bean 嘛,有什么好惊讶的,但是仔细一看,好像有没有那么简单。
混乱的 bean
所发布的应用本身注册了一个 crowdService 这样的 bean,用于查询人群命中,属于通用的中台服务,同时该应用引入了我之前写过的一个 sdk,巧合的是,sdk 内部也注册了 crowdService,但是当时为了防止 bean 冲突,已经手动修改了注册名为 customCrowdService,按道理来说不会产生冲突问题的。
问题出现在引入的另外一个 sdk,它的内部使用了这个 bean,使用方式为:
private final CrowdService crowdService;
这种通过构造器注入的方式,是怎么去寻找 bean 呢?
Spring 会查找与参数类型 CrowdService 完全匹配的 bean,如果有多个同类型的 bean,就会抛出 NoUniqueBeanDefinitionException 异常
构造器注入坑爹之处就在这里,是通过类型匹配的,虽然应用注册了两个名称不同的依赖,但是由于类型是一样的,导致了异常抛出,应用无法启动。
混乱的解决
作为一个菜鸡,阿布首先想到的解决办法是,把自己 sdk 注册这个服务的逻辑删除,统一使用应用自身所注册的服务。这种方式应该是最好的,因为 sdk 内部最好不要实现自己注册服务的逻辑,所有依赖都应该交由引入方注册,sdk 内部只负责逻辑实现。当然,这是后面才想明白的,之前我写的时候,为了偷懒以及减少跟调用方的磨嘴皮子,就直接内部注册了,自己用起来方便。
事已至此,改吧,于是拉新分支,一顿爆改,同时通知 sdk 的所有调用方,我这边逻辑改了,下次升版本的时候,记得自己把这个服务注册一下,一时间调用方无不哀声哉道,说阿布我闲得蛋疼。
就在这时,组内大佬注意到了这个情况,轻飘飘的说了一句:为什么不用 @Primary 呢?
一个注解
当 Spring 容器中存在多个相同类型的 Bean 时,@Primary 可以标记其中一个 Bean 作为默认注入的候选者.
这就是 @Primary 的作用,指定一个默认 bean,当依赖注入没有明确指定 Bean 名称(如未用 @Qualifier)时,或者是构造器注入的根据类型寻找 bean 时,Spring 会优先选择被 @Primary 标记的 Bean。
为了不给需求引入额外的改动(升级 sdk,理论上所有使用方都需要测试回归),阿布默默地回退了所有改动,在 bean 注册的地方加上了这个注解(😭)。