@Autowired注解 --required a single bean, but 2 were found出现的原因以及解决方法
@Autowired注解是spring用来支持依赖注入的核心利器之一,但是我们或多或少都会遇到required a single bean, but 2 were found(2可能是其他数字)的问题,接下来我们从源码的角度去看为什么会出现这个问题,以及这个问题的解法是什么?
首先我们写一个demo来复现一下这个问题。首先我们有一个抽象类AbstractAutowiredDemo,两个实现类AutowiredDemo1,AutowiredDemo2。然后我们在AutowiredDemoController中通过@Autowired依赖注入AbstractAutowiredDemo。
@RestController
public class AutowiredDemoController {
@Autowired
private AbstractAutowiredDemo abstractAutowiredDemo;
}
@Component
public abstract class AbstractAutowiredDemo {
public abstract String print();
}
@Component
public class AutowiredDemo2 extends AbstractAutowiredDemo {
@Override
public String print() {
return "AutowiredDemo2";
}
}
@Component
public class AutowiredDemo1 extends AbstractAutowiredDemo {
@Override
public String print() {
return "AutowiredDemo1";
}
}
此时我们启动项目就会出现如下报错,找到了两个,并且列出了找到的两个其实就是抽象类的实现类。
接下来,我们从源码的角度来看看,spring是如何查找依赖并注入的。
与之前查看@Component注解方法一致,我们全局搜索Autowired,会找到一个叫做AutowiredAnnotationBeanPostProcessor,根据命名AutowiredAnnotationXXX我们可以大概知道这个类是用来处理注解@Autowired的。
进入AutowiredAnnotationBeanPostProcessor,从注释上我们可以知道这个类可以处理注解@Autowired,@Value以及如果支持的话还有@Inject,这里我们就只用关注@Autowired就行了,其他的以后再看,并且在无参构造器中有设置支持这些类型。
然后开始进入正题,我们开始真正去看,spring是如何处理,如何查找依赖并注入的,但是我们的主线任务是为什么会出现上面的错误,这样有目的的看,先抛开其他细节,要相对容易一些。
这里可以是一个看源代码的技巧,之前的ComponentScanAnnotationParser很简单,里面只有一个parse方法,我们知道就看它,但是在AutowiredAnnotationBeanPostProcessor这个里面,这么多方法,我们应该看什么呢?首先我们要的是处理注解的方法,应该是提供出去的方法,所以应该是个pubilic方法,(我们平时编码的时候也应该是这个习惯,往外提供的public方法应该放在前面,protect,peivate这种往后面放,因为作用域越小通用性越低,用到的概率越小)。而前面的几个public方法都是在set属性值,所以排除掉,然后跟着两个看命名是跟bean定义有关的,一个是合并,一个是重置,可以暂时排出掉,然后跟着是个决定使用哪个构造器的,应该是找到bean然后实例化时候用的,接下来就是一个后置处理属性的,而我们的@Autowired就是注解在属性字段上,这里我们多看一步,看看方法的实现,有Injection of autowired dependencies字样,并且根据命名有先查元数据,再注入的过程,猜测是这个方法。
接下来着重看AutowiredAnnotationBeanPostProcessor.postProcessProperties这个方法。
首先第一行,看方法名是在查找要注入的元数据。
进入方法AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata,我们可以看到这段代码是先判断cache中是否已经有了,并且是否需要刷新(刷新其实就是为空或者类型不是clazz,可以自行点进去查看),不需要直接返回,需要就开始加锁(加锁之后又进行了一次校验,双重校验,小知识点,避免在加锁的过程中,已经put进去),再进行构建元数据buildAutowiringMetadata
进入方法AutowiredAnnotationBeanPostProcessor.buildAutowiringMetadata,看第一个判断,记不记得刚开始讲的,这个类可以处理的注解类型,这里就在判断,我们的@Autowired是肯定在其中的,然后中间又个do...while循环,将当前类以及它的父类被注解了字段,方法放入elements中,最终返回一个InjectionMetadata对象,并且设置了它的targetClass为clazz,injectedElements为elements。现在相当于我们需要进行依赖注入的元数据找到了。
接下来开始注入过程,我们回到postProcessProperties方法,查看注入方法。
进入InjectionMetadata.inject方法,我们在上面找到的元数据这里就用到的,我们不管checkedElements,至少我们的injectedElements肯定是有的,在上一步查找元数据的时候,我们已经set进去了,接下来我们就继续往下走。
当我们继续进入inject方法的时候,我们发现注释上有一句话,this和getResourceToInject都需要覆盖这个方法,所以这个方法并不是我们需要的注入方法的实现。
点击左边向下箭头,我们可以发现两个实现方法,根据命名,一个处理field的,一个处理method的,显然我们这里需要的是处理field的。
进入AutowiredFieldElement.inject方法,我们看到他先判断了是否有缓存,我们这里假设就是第一次,没有缓存(缓存肯定也是之前加载进去的),这样我们就应该走的是else分支。
进入AutowiredFieldElement.inject.resolveFieldValue方法,我们可以看到,开头是在做一些准备工作,可以忽略,最后是在将查找到的缓存起来,我们也可以不看,重点就是try中的内容,解决依赖。
进入方法AutowireCapableBeanFactory.resolveDependency,我们需要找它的实现方法,点击左边向下箭头,可以看到两个实现方法,同样根据命名,红框内的很显然是用来处理bean的。
进入DefaultListableBeanFactory.resolveDependency方法,大概扫一眼,前面都是在判断descriptor.getDependencyType()这个的值是不是那些类的类型,很显然是我们自己定义的类,都不是这些类型,所以我们直接到最后一个else,else中第一句是如果是懒加载,就先不加载了,所以真正的逻辑在下面。(其实我们就是要找到解决依赖的方法,而spring方法命名都是见文知意的,所以我们可以先直接定位到下面,发现不对再说,这是看源码时候的一个思路)
进入DefaultListableBeanFactory.doResolveDependency方法,这里就是真正的查找依赖的核心了,接下来我们仔细分析一下。
Step1:通过descriptor.resolveShortcut(this)返回shortcut,我们点进这个方法查看注释可以发现,这是用来做一些预先解析的,一般是spring自用的,我们如果没有特殊设置,一般不会用到,所以这个shortcut应该为null,方法不会返回。
Step2:通过getAutowireCandidateResolver().getSuggestedValue(descriptor)返回value,点进方法查看,根据注释看,这个是给给定依赖建议默认值的,应该处理的是@Value。所以这里value为null,方法不返回。
Step3:通过resolveMultipleBeans返回multipleBeans,可以看到里面是在判断我们当前查找的依赖的类型是否符合哪些条件(stream或者集合类型,所以这个叫multi),而我们当前的type就是我们定义的抽象类,所以这里multipleBeans也为null,方法不返回。
Step4:通过findAutowireCandidates返回matchingBeans(其实看这个方法名,就是处理Autowired注解,查找候选者的),点进方法查看。
进入方法DefaultListableBeanFactory.findAutowireCandidates,首先第一行我们可以看到在查找候选者名称。
进入方法BeanFactoryUtils.beanNamesForTypeIncludingAncestors,我们可以看到这里又调用了一个方法,通过type获取beanNames,点进去看注释可以看到这里会获取当前类型的bean的名称(会排除抽象类,不再深入进去,可以自己点进去看),包括子类,其实看到这里应该大概猜出来了,我们通过上面的抽象类AbstractAutowiredDemo拿到了它的子类,所以报错里面出现的是子类AutowiredDemo1和AutowiredDemo2。接着中间一段可能会查出更多的,但是这里我们不关心了,现在我们直接返回,此时String[]应该包含两个元素。
回到方法DefaultListableBeanFactory.findAutowireCandidates,我们可以发现,result中至少有两个元素,下面的for都是在里面继续add,这里我们不再看,继续往外走。
回到方法DefaultListableBeanFactory.doResolveDependency,matchingBeans中至少会有两个元素,则会进入下面一个if,而在if里面第一个代码就是在决定到底选用哪个候选bean,这里也是我们解决这个问题的一个切入点。
进入DefaultListableBeanFactory.determineAutowireCandidate会发现它先找了是否设置primary,priority,都没有的话就循环,查看有没有已经加载了的或者就是当前,这个最终目的就是要决定一个候选作为依赖注入,但是我们的这个案例,很显然决定不了。
回到外面方法之后,因为@Aurowired的required默认就是为true,所以一定会进入这个if,返回一个找到不唯一的异常。
总结
@Autowired注解字段查找并注入依赖的过程可以概括为:找到需要依赖注入的字段,通过class类型查找可以注入的类(包括子类),决定注入类,注入。
所以要解决文章开始出现的问题,有两个办法:
1.在查找处规避,注入的时候指定是Demo1还是Demo2
2.在决定注入类处规避,通过注解@Primary或者@Priority
@Autowired注解 --required a single bean, but 2 were found出现的原因以及解决方法的更多相关文章
- Field amqpTemplate in * required a single bean, but 3 were found:
Field amqpTemplate in * required a single bean, but 3 were found: Spring Boot 启动的时候报的错 使用Spring Boot ...
- 【记录】Field required a single bean, but 2 were found:
重构遇到个小问题,记录下: 错误信息: *************************** APPLICATION FAILED TO START ************************ ...
- Field in required a single bean, but 2 were found:
我在其他类注入的时候出现以下错误 @Autowired NodeAgentService nodeAgentService; 异常 Description: Field mibService in c ...
- Parameter 0 of method sqlSessionTemplate in org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration required a single bean, but 2 were found:
Parameter 0 of method orderSqlSessionFactory in com.config.MultipleDBConfig required a single bean, ...
- Field baseMapper in com.baomidou.mybatisplus.extension.service.impl.ServiceImpl required a single bean, but xx were found:
在学习使用 mybatis-plus 时,遇到一个奇怪的异常 如 代码一: 代码一: Error starting ApplicationContext. To display the conditi ...
- springboot多数据源启动报错:required a single bean, but 6 were found:
技术群: 816227112 参考:https://stackoverflow.com/questions/43455869/could-not-autowire-there-is-more-than ...
- Spring多个数据源问题:DataSourceAutoConfiguration required a single bean, but * were found
原因: @EnableAutoConfiguration 这个注解会把配置文件号中的数据源全部都自动注入,不会默认注入一个,当使用其他数据源时再调用另外的数据源. 解决方法: 1.注释掉这个注解 2. ...
- 在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法
参考原贴地址:https://blog.csdn.net/clementad/article/details/47339519 在同一个类中,一个方法调用另外一个有注解(比如@Async,@Trans ...
- 【转】在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法
参考 原文链接 @Transactional does not work on method level 描述 在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational) ...
随机推荐
- Android——RelativeLayout
代码:<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android= ...
- python目录索引
python目录索引 python基础数据类型1 目录 part1 part2 运算符 格式化 part3 字符串 字符串常用操作方法 part4 列表 列表的创建: 列表的索引,切片 列表的增删改查 ...
- vs2022+resharper C++ = 拥有一个不输clion的代码体验
这篇文章详细讲一下resharper C++在vs2022中的配置,让他拥有跟clion一样好用的代码补全功能. 为什么clion写代码体验很好好用为啥还要用vs呢,因为网上很多教程都是基于visua ...
- 优秀开源平台,前后端分离快速开发平台,一站式多端开发(PC+APP)
JNPF平台架构介绍 JNPF快速开发平台采用前后端分离技术.采用B/S架构开发,形成一站式开发多端(APP+PC)使用. PC端版本介绍 第一个当然是当下热门的.net core了,运行环境为Vis ...
- neo4j删除节点和关系
两种方法: 一.用下列 Cypher 语句: match (n) detach delete n 原理:匹配所有的节点,然后进行删除. 二. 从文件系统上删除对应的数据库. 1.停掉服务: 2.删除 ...
- CF908D New Year and Arbitrary Arrangement 题解
\(0.\) 前言 有一天 \(Au\) 爷讲期望都见到了此题,通过写题解来加深理解. \(1.\) 题意 将初始为空的序列的末尾给定概率添加 \(a\) 或 \(b\),当至少有 \(k\) 对 \ ...
- DYOJ 【20220303模拟赛】最少分组 题解
最少分组 题意 \(n\) 个点 \(m\) 条边的无向图,可以删掉 0 条或多条边,求满足条件的最小连通块数量: 对每个顶点对 \((a,b)\) ,若 \(a\) 和 \(b\) 同属于一个连通块 ...
- 2021.03.20【NOIP提高B组】模拟 总结
区间 DP 专场:愉快爆炸 T1 题目大意 有 \(n\) 个有颜色的块,连续 \(k\) 个相同颜色的就可以消掉 现在可以在任意位置插入任意颜色的方块,问最少插入多少个可以全部抵消 题解 先把连续的 ...
- LVGL库入门教程03-布局方式
LVGL布局方式 LVGL的布局 上一节介绍了如何在 LVGL 中创建控件.如果在创建控件时不给控件安排布局,那么控件默认会被放在父容器的左上角. 可以使用 lv_obj_set_pos(obj, x ...
- spring源码解析:元注解功能的实现
前言 众所周知,spring 从 2.5 版本以后开始支持使用注解代替繁琐的 xml 配置,到了 springboot 更是全面拥抱了注解式配置.平时在使用的时候,点开一些常见的等注解,会发现往往在一 ...