存在的问题以及解决方案

直接上案例,通过案例来看技术是如何使用的:

package com.javacode2018.lesson001.demo8;public class NormalBean {public interface IService{} //@1public static class ServiceA implements IService{} //@2public static class ServiceB implements IService{} //@3
}

上面代码很简单,@1:定义了一个接口IService,@2和@3创建了两个类都实现了IService接口。

下面我们通过spring来定义ServiceA和ServiceB两个bean,配置文件(normalBean.xml)如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsd"><bean id="serviceA" class="com.javacode2018.lesson001.demo8.NormalBean$ServiceA"/><bean id="serviceB" class="com.javacode2018.lesson001.demo8.NormalBean$ServiceB"/></beans>

来个测试用例来从spring容器中获取上面定义的bean对象,如下:

package com.javacode2018.lesson001.demo8;import com.javacode2018.lesson001.demo5.IocUtils;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!* bean元素的primary属性可以解决什么问题?*/
public class PrimaryTest {@Testpublic void normalBean() {String beanXml = "classpath:/com/javacode2018/lesson001/demo8/normalBean.xml";ClassPathXmlApplicationContext context = IocUtils.context(beanXml);//下面我们通过spring容器的T getBean(Class<T> requiredType)方法获取容器中对应的beanNormalBean.IService service = context.getBean(NormalBean.IService.class); //@1System.out.println(service);}
}

注意@1的代码,从spring容器中在容器中查找NormalBean.IService.class类型的bean对象,我们来运行一下看看效果,部分输出如下:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.javacode2018.lesson001.demo8.NormalBean$IService' available: expected single matching bean but found 2: serviceA,serviceBat org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1180)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:416)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1126)

发生异常了,错误中有一段提示比较重要,如下:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.javacode2018.lesson001.demo8.NormalBean$IService' available: expected single matching bean but found 2: serviceA,serviceB

这个详细说出了错误原因:spring容器中定义了2个bean,分别是serviceA和serviceB,这两个bean对象都实现了IService接口,而用例中我们想从容器中获取IService接口对应的bean,此时容器中有2个候选者(serviceA和serviceB)满足我们的需求,此时spring容器不知道如何选择,到底是返回serviceA呢还是返回serviceB呢?spring容器也懵逼了,所以报错了。

再来看一个通过setter方法注入的案例:

package com.javacode2018.lesson001.demo8;public class SetterBean {public interface IService{} //@1public static class ServiceA implements IService{} //@2public static class ServiceB implements IService{} //@3private IService service;public void setService(IService service) {this.service = service;}
}

下面我们通过xml来定义SetterBean,并且使用setter方式将IService注入到SetterBean中,配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsd"><bean id="serviceA" class="com.javacode2018.lesson001.demo8.SetterBean$ServiceA"/><bean id="serviceB" class="com.javacode2018.lesson001.demo8.SetterBean$ServiceA"/><bean id="setterBean" class="com.javacode2018.lesson001.demo8.SetterBean" autowire="byType" />
</beans>

注意上面setterBean的定义,autowire="byType"采用了按照类型自动注入的方式,容器启动的时候,会自动取调用SetterBean的setService方法,在容器中查找和这个方法参数类型匹配的bean,将查找的bean通过setService方法注入进去。

来个测试用例,PrimaryTest中加个方法:

@Test
public void setterBean() {String beanXml = "classpath:/com/javacode2018/lesson001/demo8/setterBean.xml";ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
}

运行输出:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'setterBean' defined in class path resource [com/javacode2018/lesson001/demo8/setterBean.xml]: Unsatisfied dependency expressed through bean property 'service'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.javacode2018.lesson001.demo8.SetterBean$IService' available: expected single matching bean but found 2: serviceA,serviceBat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1526)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1406)

错误中重点信息:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.javacode2018.lesson001.demo8.SetterBean$IService' available: expected single matching bean but found 2: serviceA,serviceB

容器中去找IService接口对应的bean,期望有一个匹配的,实际上却找到了2个匹配的,不知道如何选择,报错了。

上面2个案例报的异常都是下面这个异常:

org.springframework.beans.factory.NoUniqueBeanDefinitionException

当希望从容器中获取到一个bean对象的时候,容器中却找到了多个匹配的bean,此时spring不知道如何选择了,处于懵逼状态,就会报这个异常。

spring中可以通过bean元素的primary属性来解决这个问题,可以通过这个属性来指定当前bean为主要候选者,当容器查询一个bean的时候,如果容器中有多个候选者匹配的时候,此时spring会返回主要的候选者。

下面我们使用primary来解决上面案例的问题:

package com.javacode2018.lesson001.demo8;public class PrimaryBean {public interface IService{} //@1public static class ServiceA implements IService{} //@2public static class ServiceB implements IService{} //@3private IService service;public void setService(IService service) {this.service = service;}@Overridepublic String toString() {return "PrimaryBean{" +"service=" + service +'}';}
}

spring配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsd"><bean id="serviceA" class="com.javacode2018.lesson001.demo8.PrimaryBean$ServiceA" primary="true"/><bean id="serviceB" class="com.javacode2018.lesson001.demo8.PrimaryBean$ServiceA"/><bean id="setterBean" class="com.javacode2018.lesson001.demo8.PrimaryBean" autowire="byType" />
</beans>

上面配置中我们将serviceA的primary属性置为true了,将其置为主要候选者,容器中如果查找bean的时候,如果有多个匹配的,就以他为主。

我们来个测试用例:

@Test
public void primaryBean() {String beanXml = "classpath:/com/javacode2018/lesson001/demo8/primaryBean.xml";ClassPathXmlApplicationContext context = IocUtils.context(beanXml);PrimaryBean.IService service = context.getBean(PrimaryBean.IService.class); //@1System.out.println(service);PrimaryBean primaryBean = context.getBean(PrimaryBean.class); //@2System.out.println(primaryBean);
}

@1:从容器中查找IService类型匹配的bean,这个接口有2个实现类(ServiceA和Service2),这类在容器中都定义了,但是serviceA为主要的bean,所以这行代码会返回serviceA

@2:从容器中查找PrimaryBean类型的bean,容器中有一个,这个bean按照byType默认注入IService接口匹配的bean,注入的时候如果候选者有多个,以primary="true"的bean为主来注入,所以此处会注入service2

我们来运行一下,看看和分析的是否一致,运行输出:

com.javacode2018.lesson001.demo8.PrimaryBean$ServiceA@7b227d8d
PrimaryBean{service=com.javacode2018.lesson001.demo8.PrimaryBean$ServiceA@7b227d8d}

上面输出确实都是ServiceA,和我们分析的一致。

还有当候选者中如果有多个bean都将primary置为true,此时spring还是会懵逼的,也会报错,不知道如何选择了。

总结

当从容器中查找一个bean的时候,如果容器中出现多个Bean候选者时,可以通过primary="true"将当前bean置为首选者,那么查找的时候就会返回主要的候选者,否则将抛出异常。

案例源码

链接:https://pan.baidu.com/s/1p6rcfKOeWQIVkuhVybzZmQ
提取码:zr99

Spring系列

  1. Spring系列第1篇:为何要学spring?

  2. Spring系列第2篇:控制反转(IoC)与依赖注入(DI)

  3. Spring系列第3篇:Spring容器基本使用及原理

  4. Spring系列第4篇:xml中bean定义详解(-)

  5. Spring系列第5篇:创建bean实例这些方式你们都知道?

  6. Spring系列第6篇:玩转bean scope,避免跳坑里!

  7. Spring系列第7篇:依赖注入之手动注入

  8. Spring系列第8篇:自动注入(autowire)详解,高手在于坚持

  9. Spring系列第9篇:depend-on到底是干什么的?

更多好文章

  1. Java高并发系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. Mybatis系列(共12篇)

  5. 聊聊db和缓存一致性常见的实现方式

  6. 接口幂等性这么重要,它是什么?怎么实现?

  7. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

感谢大家的阅读,也欢迎您把这篇文章分享给更多的朋友一起阅读!谢谢!

路人甲java

▲长按图片识别二维码关注

路人甲Java:工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!

Spring系列第10篇:primary可以解决什么问题?相关推荐

  1. Spring系列第20篇:@Conditional通过条件来控制bean的注册

    面试阿里p7被问到的问题(当时我只知道第一个): @Conditional是做什么的? @Conditional多个条件是什么逻辑关系? 条件判断在什么时候执行? ConfigurationCondi ...

  2. Spring系列第9篇:depend-on到底是干什么的?

    本文主要讨论一下bean的创建和销毁的顺序,如何来干预bean的创建和销毁的顺序. 无依赖bean创建和销毁的顺序 我们先来看一下没有任何依赖的bean的创建和销毁的顺序. 下面的xml中定义了3个b ...

  3. 【Mac 教程系列第 10 篇】如何在 Mac 上破解带有密码的 ZIP 压缩文件

    这是[Mac 教程系列第 10 篇],如果觉得有用的话,欢迎关注专栏. 我们知道如果是一般的压缩文件,可以用系统自带的或者很好用的压缩工具 Keka 来解压,但如果要解压的压缩文件设置了密码,这个时候 ...

  4. 【Unity3D 教程系列第 10 篇】Unity 脚本中的生命周期流程图

    这是[Unity3D 教程系列第 10 篇],如果觉得有用的话,欢迎关注专栏. 文章目录 一:生命周期流程图 二:常用的生命周期函数 三:脚本初始化和销毁 四:脚本的动态添加与静态添加 五:Awake ...

  5. 【JavaScript 教程系列第 10 篇】判断一个数是整数还是小数

    这是[JavaScript 教程系列第 10 篇],如果觉得有用的话,欢迎关注专栏. 思路 如果一个数是整数,那么 parseInt() 函数和 parseFloat() 函数的返回值是相同的,反之返 ...

  6. 【C# 教程系列第 10 篇】c# 中如何让指定类不能被继承?

    这是[C# 教程系列第 10 篇],如果觉得有用的话,欢迎关注专栏. 有时候我们并不希望自己写的类被继承,怎么做呢? 方法很简单,只需要在定义类时加上 sealed 关键字,用 sealed 关键字声 ...

  7. 【Dart 教程系列第 10 篇】Dart 之 removeLast 删除数组的最后一个元素

    这是[Dart 教程系列第 10 篇],如果觉得有用的话,欢迎关注专栏. Dart 删除数组的最后一个元素,用 removeLast() 方法,该方法无参数,返回被删除对象. List<Stri ...

  8. 【jQuery 教程系列第 10 篇】jQuery 中的过滤选择器(基本筛选器)

    这是[jQuery 教程系列第 10 篇],如果觉得有用的话,欢迎关注专栏. jQuery 中除了一些 基本选择器 ,也有一些过滤选择器,本篇博客仅挑选其中的几个简单说明一下,其它的大家可以点击 jQ ...

  9. 【Excel 教程系列第 10 篇】Excel 2016 界面介绍

    这是[Excel 教程系列第 10 篇],如果觉得有用的话,欢迎关注专栏. 今天看视频教学时,突然意识到老师说的选项卡,名称框我听都没听过,但听讲课老师一说,才知道原来"这"就是选 ...

最新文章

  1. UMDF驱动开发入门
  2. Android SQLite数据库的基本操作-SQLiteDatabase
  3. js渲染模板html,一个javascript模板渲染组件,laytpl
  4. java 代码运行速度慢_C代码的运行速度总是比Java快,对吧? 错误!
  5. MySQL—修改数据库root用户密码
  6. 实验一缓冲区溢出漏洞实验
  7. 马克思知识点总结(一)
  8. 64. magento enable error report
  9. rails mysql优化_Ruby on Rails中的MySQL性能
  10. Unity 自学与进阶必会目录
  11. 商业智能BI的特点及发展
  12. 作为一名软件测试人员,有哪些网站是你应该多多关注的,哪些书籍是你必须要看的?
  13. VUE+Cesium绘制迁徙图结合echarts实现
  14. Android短视频SDK
  15. HTML-一些自己不常用但很有用的标签
  16. VIVADO如何手动布局布线
  17. RocketMQ 设计原理与实践
  18. 限定自身转动轴向的LookAt
  19. python 计算两个经纬度的距离_python 通过两个点的经纬度计算距离
  20. hikaricp和mysql驱动_配置HikariCP连接池

热门文章

  1. Xilinx ISERDESE2应用笔记及仿真实操
  2. 数码相机和单反相机区别
  3. 36 | 职业发展:应聘安全工程师,我需要注意什么?
  4. 【论文阅读笔记】语义三维重建CVPR2011:Semantic Structure from Motion
  5. 如何做一个软件项目经理? ----写给公司所有的开发人员
  6. 【C++】约瑟夫环问题:任给正整数n和k,按下述方法可以得到1,2, …n的一个置换:将数字1,2,…,n环形排列,按顺时针方向自1开始报数,报到K时输出该位置上的数字,并使其出列。
  7. 开源DirectShow分析器和解码器: LAV Filter
  8. 使用expdp(非本地)远程导出数据
  9. C语言大学期末考试重点,快点码住,再也不用担心挂科啦
  10. CSDN第九次竞赛题解与总结