1. 什么是自动装配的歧义性?

在Spring中,装配bean有以下3种方式:

  1. 自动装配
  2. Java配置
  3. xml配置

在这3种方式中,自动装配为我们带来了很大的便利,大大的降低了我们需要手动装配bean的代码量。

不过,自动装配也不是万能的,因为仅有一个bean匹配条件时,Spring才能实现自动装配,如果出现不止1个bean匹配条件时,Spring就会不知道要装配哪个bean,抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException异常,这就是自动装配的歧义性。

为了方便理解,我们举个具体的例子。

首先,我们新建个接口Dessert,该接口仅有1个方法showName():

  1. package chapter03.ambiguity;
  2. public interface Dessert {
  3. void showName();
  4. }

然后定义3个该接口的实现类Cake,Cookies,IceCream:

  1. package chapter03.ambiguity;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. public class Cake implements Dessert {
  5. @Override
  6. public void showName() {
  7. System.out.println("蛋糕");
  8. }
  9. }
  1. package chapter03.ambiguity;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. public class Cookies implements Dessert {
  5. @Override
  6. public void showName() {
  7. System.out.println("饼干");
  8. }
  9. }
  1. package chapter03.ambiguity;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. public class IceCream implements Dessert {
  5. @Override
  6. public void showName() {
  7. System.out.println("冰激凌");
  8. }
  9. }

然后新建甜点店类DessertShop,该类的setDessert()方法需要装配1个Dessert的实例bean:

  1. package chapter03.ambiguity;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class DessertShop {
  6. private Dessert dessert;
  7. public Dessert getDessert() {
  8. return dessert;
  9. }
  10. @Autowired
  11. public void setDessert(Dessert dessert) {
  12. this.dessert = dessert;
  13. }
  14. public void showDessertName() {
  15. this.dessert.showName();
  16. }
  17. }

不过现在符合装配条件的有3个bean,它们的bean ID(默认情况下是类名首字母小写)分别为cake,cookies,iceCream,Spring该自动装配哪个呢?

带着这个疑问,我们先新建配置类AmbiguityConfig:

  1. package chapter03.ambiguity;
  2. import org.springframework.context.annotation.ComponentScan;
  3. @ComponentScan
  4. public class AmbiguityConfig {
  5. }

这个类的关键是添加了@ComponentScan注解,让Spring自动扫描已经定义好的bean。

最后,新建类Main,在其main()方法中添加如下测试代码:

  1. package chapter03.ambiguity;
  2. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  3. public class Main {
  4. public static void main(String[] args) {
  5. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AmbiguityConfig.class);
  6. DessertShop dessertShop = context.getBean(DessertShop.class);
  7. dessertShop.showDessertName();
  8. context.close();
  9. }
  10. }

运行代码,发现抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException异常,如下所示:

那么如何解决自动装配的歧义性呢?Spring提供了以下2种方案:

  1. 标记首选的bean
  2. 使用限定符

2. 标记首选的bean

既然现在有3个匹配条件的bean,我们可以通过@Primary注解标记下哪个是首选的bean,这样当Spring发现有不止1个匹配条件的bean时,就会选择这个首选的bean。

比如3种甜点里,我最喜欢吃饼干,那么我就把Cookies标记为首选的bean:

  1. package chapter03.ambiguity;
  2. import org.springframework.context.annotation.Primary;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. @Primary
  6. public class Cookies implements Dessert {
  7. @Override
  8. public void showName() {
  9. System.out.println("饼干");
  10. }
  11. }

再次运行测试代码,输出结果如下所示:

饼干

圆满解决了歧义性的问题,不过有一天,有个同事不小心在IceCream上也添加了@Primary注解:

  1. package chapter03.ambiguity;
  2. import org.springframework.context.annotation.Primary;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. @Primary
  6. public class IceCream implements Dessert {
  7. @Override
  8. public void showName() {
  9. System.out.println("冰激凌");
  10. }
  11. }

编译都正常,因此都没注意,但发布后运行时,却抛出如下异常:

意思就是发现了不止1个首选的bean,因为此时Spring又不知道该选择哪个了,也就是有了新的歧义性,所以甩锅抛出了异常。

3. 使用限定符

3.1 基于bean ID的限定符

Spring还提供了另一个注解@Qualifier注解来解决自动装配的歧义性,它可以与@Autowired或者@Inject一起使用,在注入的时候指定想要注入哪个bean。

比如,我们把IceCream注入到setDessert()的方法参数之中:

  1. @Autowired
  2. @Qualifier("iceCream")
  3. public void setDessert(Dessert dessert) {
  4. this.dessert = dessert;
  5. }

这里传递的iceCream指的是IceCream类默认生成的bean ID。

再次运行测试代码,输出结果如下所示:

冰激凌

我们可以发现,使用了@Qualifier注解后,我们之前标记的@Primary注解被忽略了,也就是说,@Qualifier注解的优先级比@Primary注解的优先级高。

使用默认的限定符虽然解决了问题,不过可能会引入一些问题。比如我在重构代码时,将IceCream类名修改成了Gelato:

  1. package chapter03.ambiguity;
  2. import org.springframework.context.annotation.Primary;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. @Primary
  6. public class Gelato implements Dessert {
  7. @Override
  8. public void showName() {
  9. System.out.println("冰激凌");
  10. }
  11. }

此时运行代码,会发现抛出org.springframework.beans.factory.NoSuchBeanDefinitionException异常,如下所示:

这是因为IceCream重命名为Gelato之后,bean ID由iceCream变成了gelato,但我们注入地方的代码仍然使用的是iceCream这个bean ID,导致没有找到匹配条件的bean。

鉴于使用默认的限定符的这种局限性,我们可以使用自定义的限定符来解决这个问题。

为不影响后面代码的测试结果,将Gelato类再改回IceCream

3.2 基于面向特性的限定符

为了避免因为修改类名而导致自动装配失效的问题,我们可以在@Component或者@Bean注解声明bean时添加上@Qualifier注解,如下所示:

  1. package chapter03.ambiguity;
  2. import org.springframework.beans.factory.annotation.Qualifier;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. @Qualifier("cold")
  6. public class IceCream implements Dessert {
  7. @Override
  8. public void showName() {
  9. System.out.println("冰激凌");
  10. }
  11. }

然后在注入的地方,不再使用默认生成的bean ID,而是使用刚刚指定的cold限定符:

  1. @Autowired
  2. @Qualifier("cold")
  3. public void setDessert(Dessert dessert) {
  4. this.dessert = dessert;
  5. }

运行测试代码,输入结果如下所示:

冰激凌

此时将IceCream类重命名为Gelato,代码可以正常运行,不会受影响。

然后有一天,某位开发又新建了类Popsicle,该类也使用了cold限定符:

  1. package chapter03.ambiguity;
  2. import org.springframework.beans.factory.annotation.Qualifier;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. @Qualifier("cold")
  6. public class Popsicle implements Dessert {
  7. @Override
  8. public void showName() {
  9. System.out.println("棒冰");
  10. }
  11. }

此时又带来了新的歧义性问题,因为Spring又不知道该如何选择了,运行代码会抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException异常,如下所示:

此时,我们就需要用到自定义的限定符了。

3.3 自定义的限定符注解

首先,我们新建以下3个注解:

  1. package chapter03.ambiguity;
  2. import org.springframework.beans.factory.annotation.Qualifier;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Qualifier
  10. public @interface Cold {
  11. }
  1. package chapter03.ambiguity;
  2. import org.springframework.beans.factory.annotation.Qualifier;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Qualifier
  10. public @interface Creamy {
  11. }
  1. package chapter03.ambiguity;
  2. import org.springframework.beans.factory.annotation.Qualifier;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Qualifier
  10. public @interface Fruity {
  11. }

注意事项:这3个注解在定义时都添加了@Qualifier注解,因此它们具有了@Qualifier注解的特性

然后将IceCream类修改为:

  1. package chapter03.ambiguity;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. @Cold
  5. @Creamy
  6. public class IceCream implements Dessert {
  7. @Override
  8. public void showName() {
  9. System.out.println("冰激凌");
  10. }
  11. }

将Popsicle类修改为:

  1. package chapter03.ambiguity;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. @Cold
  5. @Fruity
  6. public class Popsicle implements Dessert {
  7. @Override
  8. public void showName() {
  9. System.out.println("棒冰");
  10. }
  11. }

最后,修改下注入地方的代码,使其只能匹配到1个满足条件的bean,如下所示:

  1. @Autowired
  2. @Cold
  3. @Creamy
  4. public void setDessert(Dessert dessert) {
  5. this.dessert = dessert;
  6. }

运行测试代码,输出结果如下所示:

冰激凌

由此,我们也可以发现,自定义注解与@Qualifier注解相比,有以下2个优点:

  1. 可以同时使用多个自定义注解,但@Qualifier注解只能使用1个
  2. 使用自定义注解比@Qualifier注解更为类型安全

4. 源码及参考

源码地址:https://github.com/zwwhnly/spring-action.git,欢迎下载。

Craig Walls 《Spring实战(第4版)》

原创不易,如果觉得文章能学到东西的话,欢迎点个赞、评个论、关个注,这是我坚持写作的最大动力。

如果有兴趣,欢迎添加我的微信:zwwhnly,等你来聊技术、职场、工作等话题(PS:我是一名奋斗在上海的程序员)。

Spring入门(八):自动装配的歧义性的更多相关文章

  1. Spring实战之处理自动装配的歧义性

    仅有一个bean匹配所需的结果时,自动装配才是有效的.如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍Spring自动装配属性.构造器参数或方法参数.为了阐述自动装配的歧义性,假设我们使用@A ...

  2. spring装配---处理自动装配的歧义性

    一.歧义性 当我们使用spring的注解进行自动装配bean时,如果不仅有一个bean能够匹配结果的话,会抛出NoUniqueBeanDefinitionException: 例如本例中 当sprin ...

  3. spring学习总结——高级装配学习二(处理自动装配的歧义性)

    我们已经看到如何使用自动装配让Spring完全负责将bean引用注入到构造参数和属性中.自动装配能够提供很大的帮助.不过,spring容器中仅有一个bean匹配所需的结果时,自动装配才是有效的.如果不 ...

  4. Spring实战(六)自动装配的歧义性

    1.Spring进行自动装配时碰到的bean歧义性问题. 在进行自动装配时,只有仅有一个bean匹配所需结果时,才是可行的. 如果不仅仅一个bean能够匹配结果,例如一个接口有多个实现,这种歧义性会阻 ...

  5. Spring-处理自动装配的歧义性

    自动装配可以对依赖注入提供很大帮助,因为它会减少装配应用程序组件时所需的显式装配的数量. 不过,仅有一个bean匹配所需的结果时,自动装配才是有效的.如果不仅有一个bean能够匹配的话,这种歧义性会阻 ...

  6. SpringInAction--Bean自动装配的歧义性处理

    在前面,学习如何装配Bean的时候,或许会发现,有的同类型的Bean智能配置一个 如下: package com.bean.java; import org.springframework.conte ...

  7. Spring处理自动装配的歧义性

    1.标识首选的bean 2.使用限定符@Qualifier 首先在bean的声明上添加@Qualifier 注解: @Component @Qualifier("cdtest") ...

  8. Spring(六)之自动装配

    一.自动装配模型 下面是自动连接模式,可以用来指示Spring容器使用自动连接进行依赖注入.您可以使用元素的autowire属性为bean定义指定autowire模式. 可以使用 byType 或者  ...

  9. Spring 由构造函数自动装配

    Spring 由构造函数自动装配,这种模式与 byType 非常相似,但它应用于构造器参数. Spring 容器看作 beans,在 XML 配置文件中 beans 的 autowire 属性设置为 ...

随机推荐

  1. c++学习书籍推荐《Exceptional C++ Style》下载

    百度云及其他网盘下载地址:点我 编辑推荐 软件“风格”所要讨论的主题是如何在开销与功能之间.优雅与可维护性之间.灵活.性与过分灵活之间寻找完美的平街点.在本书中,著名的C++大师Herb Sutter ...

  2. C# ExcelAddIn 开发笔记

    好久都没有写博客了,最近真的是太忙了,接手公司要做的一个小的新的项目,从接触认识到一个新东西,再到自己琢磨研究,最终结合公司业务把整个excel插件项目完成,还是有一点点成就感.以下是项目中基本上大致 ...

  3. 《C Primer Plus(第6版)中文版》勘误

    搬运自己2016年11月28日发布于SegmentFault的文章.链接:https://segmentfault.com/a/1190000007626460 本勘误由本人整理并发布,仅针对下方列出 ...

  4. c++小游戏——贪吃蛇

    #include #include #include #include #include <conio.h> #include #include <windows.h> usi ...

  5. 个人永久性免费-Excel催化剂功能第58波-批量生成单选复选框

    插件的最大威力莫过于可以把简单重复的事情批量完全,对日常数据采集或打印报表排版过程中,弄个单选.复选框和用户交互,美观的同时,也能保证到数据采集的准确性,一般来说用原生的方式插入单选.复选框,操作繁琐 ...

  6. 阿里百川HotFix2.0热修复初体验

    博客原地址:http://blog.csdn.net/allan_bst/article/details/72904721 一.什么是热修复 热修复说白了就是"打补丁",比如你们公 ...

  7. [剑指offer] 16. 合并两个排序的链表

    题目描述 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则. 解法一: 非递归解 class Solution { public: ListNode *Mer ...

  8. 《C# 语言学习笔记》——目录

    C# 简介 变量和表达式 流程控制 3.1 布尔逻辑 3.2 goto语句 3.3 分支 3.4 循环 变量的更多内容 4.1 类型转换 4.2 复杂的变量类型 4.3 字符串的处理 函数 5.1 定 ...

  9. Uploadify.js引用导致浏览器宽度计算错误,布局混乱

    首先,本人新手,高手勿喷,请忽略.谢谢. 今天在写代码的时候遇到一个奇葩问题,我再在页面加载完成以后,动态计算DIV宽度,将整个层铺满浏览器.一切正常.单当我引入jquery.uploadify.js ...

  10. Liunx环境下配置matplotlib库使用中文绘图

    最近在使用matplotlib库的过程中需要用到中文绘图,在网上找了好多种方法,最终用一种方法解决了,在此记录. 首先Linux是有自己的中文字体的,叫做"Droid Sans Fallba ...