首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >interface注入及报错分析

interface注入及报错分析

作者头像
LiosWong
发布于 2018-10-29 09:41:38
发布于 2018-10-29 09:41:38
94600
代码可运行
举报
文章被收录于专栏:后端沉思录后端沉思录
运行总次数:0
代码可运行

一个小case

上面错误原因我想大家开发中都遇到过,大致错误原因是注入bean时,spring找到2个实例userServiceImplTest、userServiceImpl,无法确认到底使用哪个。问题出在这,原因是什么呢,在说明前,看下面的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestController
public class OkController {
@Autowired
UserService userService;
@ResponseBody
@GetMapping(value = "/ok")
public String ok(){
    UserInfoEntity userInfoEntity = userService.selectByTel("lioswang");
}
//此时项目中UserService的实现类只有UserServiceImpl
@Service
public class UserServiceImpl implements UserService{
    @Override
    public UserInfoEntity selectByTel(String tel) {
        return  null;
    }
}

在OkController中为什么可以直接注入接口,当项目启动时,调用了UserServiceImpl类中的selectByTel方法,由于在OkController中引用了UserService,所以锁定在OkController初始化时Spring到底干了些什么,根据之前源码分析的经验,在 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法中打上条件断点,首先看方法 org.springframework.beans.factory.support.AbstractBeanFactory#createBean,调用了方法org.springframework.beans.factory.support.AbstractBeanFactory#createBean,继续跟进方法 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean, 再跟进去方法 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean,继续跟进去 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues,在方法中找到OkController注入的元数据UserService,调用了 org.springframework.beans.factory.annotation.InjectionMetadata#inject,跟进去 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject,继续跟 org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency,其中代码片段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//获取接口的依赖
result = doResolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);

调用了 org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency,该方法的代码片段

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);

继续跟 org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates,其中获取UserService所有的实现类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//获取到UserService的实现类
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());
//获取到实现类后,并初始化,保存在Map<String, Object>
result.put(candidateName, getBean(candidateName));
代码语言:javascript
代码运行次数:0
运行
复制

再看 org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency方法中代码片段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
    if (descriptor.isRequired()) {
        raiseNoSuchBeanDefinitionException(type, "", descriptor);
    }
    return null;
}
//获取匹配到的bean数大于1时的逻辑处理
if (matchingBeans.size() > 1) {
    String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
    if (primaryBeanName == null) {
        throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
    }
    if (autowiredBeanNames != null) {
        autowiredBeanNames.add(primaryBeanName);
    }
    return matchingBeans.get(primaryBeanName);
}
// We have exactly one match.
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
if (autowiredBeanNames != null) {
    autowiredBeanNames.add(entry.getKey());
}
return entry.getValue();
}

由于目前项目中UserService的实现类只有UserServiceImpl,所以最终获取到的只有一个。 再回到 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject中,已经找到UserService的实现类,所以执行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
代码语言:javascript
代码运行次数:0
运行
复制

即把UserServiceImpl的实例设置到属性UserService中。 所以当再OkController中调用UserService的selectByTel方法,其实调用的是UserServiceImpl的selectByTel方法。

报错

上面分析那么多,其实就是为了说明我们注入接口时,为什么会调用实现类的方法。为了报错,很简单,再写一个类实现UserService接口即可,OkController中不需要修改,其实由上面的分析知道,报错的就是上面的这段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (matchingBeans.size() > 1) {
    String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
    if (primaryBeanName == null) {
        throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
    }
    if (autowiredBeanNames != null) {
        autowiredBeanNames.add(primaryBeanName);
    }
    return matchingBeans.get(primaryBeanName);
}

也就是在UserService的实现类中找到多个bean实例,这个明显是错误的,

错误解决

如何解决这个问题呢,很简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Autowired
@Qualifier("userServiceImpl")
UserService userService;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Resource(name = "userServiceImpl")
UserService userService;

因为在 org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates方法中的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for (String candidateName : candidateNames) {
    if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
        result.put(candidateName, getBean(candidateName));
    }
}

会根据注解过滤bean,所以加上上面的注解后会解决错误,具体代码就不分析了,感兴趣的同学可打断点调试。

思考拓展

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestController
public class OkController {
@Autowired
Map<String,UserService> userServiceMap;
@ResponseBody
@GetMapping(value = "/ok")
public String ok(){
   ...
}

若OkController中代码修改如上,项目启动后,发现没有报错,而且userServiceMap中有两个key-value元素,无疑是UserServiceImpl、UserServiceImplTest,我想原因不难看出, org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates返回了Map,其值和userServiceMap相同,不难看出spring功能非常强大。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-10-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端沉思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
简话bean加载
首先看示例代码: <!--no-lazy-init scope=singleton--> <bean class="com.lios.service.test.LiosTestA" id="liosTestA"/> <bean class="com.lios.service.test.LiosTestB" id="liosTestB"/> <bean class="com.lios.service.test.LiosServiceServiceImpl" id="liosServiceService"/
LiosWong
2018/10/29
4170
Spring的@Autowired依赖注入原来这么多坑!
经常会遇到,required a single bean, but 2 were found。
JavaEdge
2021/12/07
7230
Spring的@Autowired依赖注入原来这么多坑!
Spring的自动装配
坏处:依赖不能明确管理,可能会有多个bean同时符合注入规则,没有清晰的依赖关系。
SerMs
2022/04/11
6700
Spring的自动装配
Spring依赖注入@Autowired深层原理、源码级分析,感受DI带来的编程之美【享学Spring】
关于Spring IOC的依赖注入(DI机制),之前虽有过分析,但总感觉一直落了一块:对@Autowired注解元数据的解析部分。
YourBatman
2019/09/03
2.2K2
Spring依赖注入@Autowired深层原理、源码级分析,感受DI带来的编程之美【享学Spring】
Spring解决循环依赖
1、Husband创建Bean,先判断缓存池中是否存在,存在直接返回,不存在进入createBean创建的流程,调用构造方法创建一个早期的Bean【未进行属性赋值】,创建成功将其放入二级缓存earlySingletonObjects中,之后又调用addSingletonFactory方法将其放入三级缓存中并且将二级缓存中的移除,之后调用populateBean为属性赋值,在@Autowired的后置处理器中查找需要注入的依赖,发现Husband中的一个属性Wife,因此调用getBean方法从容器中获取,但是此时的Wife还未创建,因此又进入了doGetBean的流程,但是此时Wife并没有创建,因此在一二三级缓存中不能获取,又执行createBean方法创建Wife,同样调用构造方法创建一个早期Bean放入二级缓存中,调用addSingletonFactory放入三级缓存并移除二级缓存,然后调用populateBean方法为Wife属性赋值,在@Autowired的后置处理器中查找需要注入的依赖,发现Wife类中有一个属性是Husband,因此调用getBean方法,再次调用doGetBean获取Husband,但是此时的Husband已经创建成功【未赋值】,存放在三级缓存中,因此直接从三级缓存中取出Husband赋值给Wife属性,至此Wife属性已经赋值成功,直接添加到一级缓存(singletonObjects)中并且移除三级缓存,直接返回给Husband赋值,因此Husband中的属性也持有了Wife的引用,都创建并且赋值成功了。
爱撒谎的男孩
2019/12/31
1K0
你所不知道的Spring的@Autowired实现细节
相信很多Java开发都遇到过一个面试题:Resource和Autowired的区别是什么?这个问题的答案相信基本都清楚,但是这两者在Spring中是如何实现的呢?这就要分析Spring源码才能知道了。友情提示:本篇主要是讲解Autowired的实现原理,不会分析Spring初始化的过程,不熟悉的读者可以先阅读笔者之前的一篇文章《这一次搞懂Spring的Bean实例化原理》。
夜勿语
2020/09/07
6540
深入解析 Spring Framework 中 @Autowired 注解的实现原理
@Autowired 注解在Spring中的作用是实现依赖注入(Dependency Injection),它用于自动装配(autowiring)Spring Bean 的依赖关系。具体来说, @Autowired 注解有以下作用:
关忆北.
2023/10/31
2.2K0
深入解析 Spring Framework 中 @Autowired 注解的实现原理
透过源码,捋清楚循环依赖到底是如何解决的!
关于 Spring 循环依赖,松哥已经连着发了三篇文章了,本篇文章松哥从源码的角度来和小伙伴们捋一捋 Spring 循环依赖到底是如何解决了。如果没看过前面的文章建议先看一下,大家在面试中如果遇到循环依赖相关的问题,其实看前面三篇文章就可以答出来了,本文主要是从源码角度来验证一下我们前面文章所讲的内容是无误的。
江南一点雨
2023/09/09
2860
透过源码,捋清楚循环依赖到底是如何解决的!
@Autowired 与 @Resource 有何不同
@Autowired来自于 spring-beans 模块;而@Resource则来自于 jakarta.annotation-api 模块,它是 Jakarta EE 规范中的内容。虽然 @Autowired 与 @Resource 均用于实现依赖注入,但 Spring 对二者的处理逻辑是不一样的。
程序猿杜小头
2023/03/05
7680
@Autowired 与 @Resource 有何不同
@Autowired通过源码进行原理详解
IOC,inversion of control 控制反转,有时候也叫DI,dependency injection 依赖注入,是一种代码解耦的方式。
田维常
2019/07/16
2.5K0
@Autowired 到底是怎么把变量注入进来的?
在 Spring 容器中,当我们想给某一个属性注入值的时候,有多种不同的方式,例如可以通过构造器注入、可以通过 set 方法注入,也可以使用 @Autowired、@Inject、@Resource 等注解注入。
江南一点雨
2023/09/09
5180
@Autowired 到底是怎么把变量注入进来的?
spring+mybatis启动NoClassDefFoundError异常分析三部曲之二:定位错误
程序员欣宸
2018/01/04
2.6K0
spring+mybatis启动NoClassDefFoundError异常分析三部曲之二:定位错误
Spring 容器 17 个常用注解总结
传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事物,这么做有两个缺点:
江南一点雨
2019/08/13
7970
Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor
Java微观世界
2025/01/21
4390
Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor
Spring注解@Autowired源码分析
该方法就是在属性注入populateBean中调用的pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);的具体实现之一。
周同学
2020/10/16
1.2K0
Spring注解@Autowired源码分析
Spring 注入 Bean 到 List / Map 中
Spring在注入集合类的同时,会将集合泛型类的实例填入集合中,作为集合的初始值。 对于list、set填入的是注入类型Spring管理的实例,对于map,Spring会将service的名字作为key,对象作为value封装进入Map。
一个会写诗的程序员
2020/02/13
2.8K0
Error creating bean with name ‘userServiceImpl‘: Unsatisfied dependency expressed through field ‘bas
如果不行试试这个: https://blog.csdn.net/qq_42055933/article/details/125687766?spm=1001.2014.3001.5502
默 语
2024/11/20
2840
Error creating bean with name ‘userServiceImpl‘: Unsatisfied dependency expressed through field ‘bas
Spring 中 @Primary 注解的原理是什么?
除了这三个,还有没有其他办法呢?必须有!!!在 Spring 中 @Qualifier 注解还能这么用? 一文中,松哥还和大家扩展了 @Qualifier 注解的其他用法,感兴趣的小伙伴不要错过哦。
江南一点雨
2023/09/09
4990
Spring 中 @Primary 注解的原理是什么?
【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“
@Value注解是Spring3.0后提出来的一个注解。注解内容本身非常之简单,但是它提供的功能却非常之强大。
YourBatman
2019/09/03
5.3K0
【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“
spring循环依赖-不仅仅是八股文
spring的循环依赖问题是面试的时候经常会碰到的问题。相信很多朋友都看相关spring使用三级缓存解决循环依赖的博文。面试官问你的时候除了想要了解你对spring框架的熟悉程度,还想要了解你对spring循环依赖的思考。
柏炎
2022/08/23
5060
spring循环依赖-不仅仅是八股文
推荐阅读
相关推荐
简话bean加载
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档