@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出现的原因以及解决方法的更多相关文章

  1. 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 ...

  2. 【记录】Field required a single bean, but 2 were found:

    重构遇到个小问题,记录下: 错误信息: *************************** APPLICATION FAILED TO START ************************ ...

  3. Field in required a single bean, but 2 were found:

    我在其他类注入的时候出现以下错误 @Autowired NodeAgentService nodeAgentService; 异常 Description: Field mibService in c ...

  4. 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, ...

  5. 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 ...

  6. springboot多数据源启动报错:required a single bean, but 6 were found:

    技术群: 816227112 参考:https://stackoverflow.com/questions/43455869/could-not-autowire-there-is-more-than ...

  7. Spring多个数据源问题:DataSourceAutoConfiguration required a single bean, but * were found

    原因: @EnableAutoConfiguration 这个注解会把配置文件号中的数据源全部都自动注入,不会默认注入一个,当使用其他数据源时再调用另外的数据源. 解决方法: 1.注释掉这个注解 2. ...

  8. 在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法

    参考原贴地址:https://blog.csdn.net/clementad/article/details/47339519 在同一个类中,一个方法调用另外一个有注解(比如@Async,@Trans ...

  9. 【转】在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法

    参考 原文链接 @Transactional does not work on method level 描述 在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational) ...

  10. @Transational)的方法,注解失效的原因和解决方法

    在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解是不会生效的. 比如,下面代码例子中,有两方法,一个有@Transational注解,一个没有.如果 ...

随机推荐

  1. HAOI2017前

    NOIP后想换一个看起来比较高端的博客,于是尝试用github+hexo建站,地址大概是这样的,搞了一周左右.. 最终以失败告终,一是代码高亮有bug,二是数学公式有bug,三是博文没法快速搬迁QAQ ...

  2. UIweib的简单实用

  3. Think Python - Chapter 18 - Inheritance

    In this chapter I present classes to represent playing cards, decks of cards, and poker hands.If you ...

  4. Android系统进程间通信(IPC)机制Binder中的Server启动过程源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6629298 在前面一篇文章浅谈Android系 ...

  5. WCF开发实战系列五:创建WCF客户端程序

    WCF开发实战系列五:创建WCF客户端程序 (原创:灰灰虫的家http://hi.baidu.com/grayworm) 在前面的三篇文章中我们分别介绍了WCF服务的三种载体:IIS.Self-Hos ...

  6. stenciljs 学习三 组件生命周期

    stenciljs 组件包含好多生命周期方法, will did load update unload 实现生命周期的方法比价简单类似 componentWillLoad ....,使用typescr ...

  7. IocPerformance 常见IOC 功能、性能比较

    IocPerformance IocPerformance 基本功能.高级功能.启动预热三方面比较各IOC,可以用作选型参考. Lamar: StructureMap的替代品 Lamar 文档 兼容S ...

  8. java 安装配置时出现的问题

    Error: could not open `C:\Program Files\Java\jre6\lib\i386\jvm.cfg') jdkerror  前些日子装了个jdk7试了试,后来做项目需 ...

  9. springboot中 后端跨域的实现配置

    在springboot的启动文件中,添加此内容,可以允许跨域

  10. 模拟:HDU1034-Candy Sharing Game

    解题心得: 1.直接模拟每一次分一半就行了,模拟过程,记录轮数,但是也看到有些大神使用的是链表,估计链表才是真的做法吧. 题目: Candy Sharing Game Time Limit: 2000 ...