interface注入及报错分析

一个小case

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

@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,其中代码片段:

//获取接口的依赖
result = doResolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);

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

Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);

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

//获取到UserService的实现类
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());
//获取到实现类后,并初始化,保存在Map<String, Object>
result.put(candidateName, getBean(candidateName));

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

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的实现类,所以执行:

ReflectionUtils.makeAccessible(field);
field.set(bean, value);

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

报错

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

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实例,这个明显是错误的,

错误解决

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

@Autowired
@Qualifier("userServiceImpl")
UserService userService;

@Resource(name = "userServiceImpl")
UserService userService;

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

for (String candidateName : candidateNames) {
    if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
        result.put(candidateName, getBean(candidateName));
    }
}

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

思考拓展

@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功能非常强大。

原文发布于微信公众号 - 后端沉思录(LiosWangs)

原文发表时间:2018-10-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

频繁产生对象造成gc时间过长案例分析

gc时间过长,平均gc pause的时间要将近4秒,有13%的gc超过10秒,太可怕了,部分gc日志如下:

1551
来自专栏wannshan(javaer,RPC)

dubbo监控机制之监控中心实现分析

这里的监控中心以dubbo-ops\dubbo-monitor-simple项目说 总的来说是监控中心启动一个sevlet容器,通过web页面向用户多维度的展...

1.1K6
来自专栏瓜大三哥

Yaffs_guts(三)

1.垃圾回收 1.static int yaffs_InitialiseBlocks(yaffs_Device *dev,int nBlocks)//块初始化 ...

2195
来自专栏娱乐心理测试

音乐播放小程序demo

1836
来自专栏魂祭心

原 结合源码分析 setTimeout /

3356
来自专栏Hongten

spring+hibernate+JQuery开发_电子相册_源码

=============================================================

3914
来自专栏技术墨客

Spring核心——IOC处理器扩展 原

Spring一直标注自己是一个非侵入式框架。非侵入式设计的概念并不新鲜,目标就是降低使用者和框架代码的耦合,毕竟框架的开发者和使用者几乎肯定不是同一个团队。Sp...

612
来自专栏JackieZheng

Spring读书笔记——bean加载

我们的日常开发几乎离不开Spring,他为我们的开发带来了很大的便捷,那么Spring框架是如何做到方便他人的呢。今天就来说说bean如何被加载加载。 我们在x...

2159
来自专栏编码小白

ofbiz实体引擎(九) 多租户

多租户在平台中是根据delegator不同操作不同的数据库 /** * @author 郑小康 * 设置完整的delegator 其可...

3344
来自专栏alexqdjay

Springboot 随笔(2)-- Properties 配置一坑

2867

扫码关注云+社区