Spring入门(八):自动装配的歧义性
1. 什么是自动装配的歧义性?
在Spring中,装配bean有以下3种方式:
- 自动装配
- Java配置
- xml配置
在这3种方式中,自动装配为我们带来了很大的便利,大大的降低了我们需要手动装配bean的代码量。
不过,自动装配也不是万能的,因为仅有一个bean匹配条件时,Spring才能实现自动装配,如果出现不止1个bean匹配条件时,Spring就会不知道要装配哪个bean,抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException
异常,这就是自动装配的歧义性。
为了方便理解,我们举个具体的例子。
首先,我们新建个接口Dessert,该接口仅有1个方法showName():
package chapter03.ambiguity;
public interface Dessert {
void showName();
}
然后定义3个该接口的实现类Cake,Cookies,IceCream:
package chapter03.ambiguity;
import org.springframework.stereotype.Component;
@Component
public class Cake implements Dessert {
@Override
public void showName() {
System.out.println("蛋糕");
}
}
package chapter03.ambiguity;
import org.springframework.stereotype.Component;
@Component
public class Cookies implements Dessert {
@Override
public void showName() {
System.out.println("饼干");
}
}
package chapter03.ambiguity;
import org.springframework.stereotype.Component;
@Component
public class IceCream implements Dessert {
@Override
public void showName() {
System.out.println("冰激凌");
}
}
然后新建甜点店类DessertShop,该类的setDessert()方法需要装配1个Dessert的实例bean:
package chapter03.ambiguity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DessertShop {
private Dessert dessert;
public Dessert getDessert() {
return dessert;
}
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
public void showDessertName() {
this.dessert.showName();
}
}
不过现在符合装配条件的有3个bean,它们的bean ID(默认情况下是类名首字母小写)分别为cake,cookies,iceCream,Spring该自动装配哪个呢?
带着这个疑问,我们先新建配置类AmbiguityConfig:
package chapter03.ambiguity;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class AmbiguityConfig {
}
这个类的关键是添加了@ComponentScan
注解,让Spring自动扫描已经定义好的bean。
最后,新建类Main,在其main()方法中添加如下测试代码:
package chapter03.ambiguity;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AmbiguityConfig.class);
DessertShop dessertShop = context.getBean(DessertShop.class);
dessertShop.showDessertName();
context.close();
}
}
运行代码,发现抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException
异常,如下所示:
那么如何解决自动装配的歧义性呢?Spring提供了以下2种方案:
- 标记首选的bean
- 使用限定符
2. 标记首选的bean
既然现在有3个匹配条件的bean,我们可以通过@Primary
注解标记下哪个是首选的bean,这样当Spring发现有不止1个匹配条件的bean时,就会选择这个首选的bean。
比如3种甜点里,我最喜欢吃饼干,那么我就把Cookies标记为首选的bean:
package chapter03.ambiguity;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
@Primary
public class Cookies implements Dessert {
@Override
public void showName() {
System.out.println("饼干");
}
}
再次运行测试代码,输出结果如下所示:
饼干
圆满解决了歧义性的问题,不过有一天,有个同事不小心在IceCream上也添加了@Primary
注解:
package chapter03.ambiguity;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
@Primary
public class IceCream implements Dessert {
@Override
public void showName() {
System.out.println("冰激凌");
}
}
编译都正常,因此都没注意,但发布后运行时,却抛出如下异常:
意思就是发现了不止1个首选的bean,因为此时Spring又不知道该选择哪个了,也就是有了新的歧义性,所以甩锅抛出了异常。
3. 使用限定符
3.1 基于bean ID的限定符
Spring还提供了另一个注解@Qualifier
注解来解决自动装配的歧义性,它可以与@Autowired
或者@Inject
一起使用,在注入的时候指定想要注入哪个bean。
比如,我们把IceCream注入到setDessert()的方法参数之中:
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
这里传递的iceCream指的是IceCream类默认生成的bean ID。
再次运行测试代码,输出结果如下所示:
冰激凌
我们可以发现,使用了@Qualifier
注解后,我们之前标记的@Primary
注解被忽略了,也就是说,@Qualifier
注解的优先级比@Primary
注解的优先级高。
使用默认的限定符虽然解决了问题,不过可能会引入一些问题。比如我在重构代码时,将IceCream类名修改成了Gelato:
package chapter03.ambiguity;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
@Primary
public class Gelato implements Dessert {
@Override
public void showName() {
System.out.println("冰激凌");
}
}
此时运行代码,会发现抛出org.springframework.beans.factory.NoSuchBeanDefinitionException
异常,如下所示:
这是因为IceCream重命名为Gelato之后,bean ID由iceCream变成了gelato,但我们注入地方的代码仍然使用的是iceCream这个bean ID,导致没有找到匹配条件的bean。
鉴于使用默认的限定符的这种局限性,我们可以使用自定义的限定符来解决这个问题。
为不影响后面代码的测试结果,将Gelato类再改回IceCream
3.2 基于面向特性的限定符
为了避免因为修改类名而导致自动装配失效的问题,我们可以在@Component或者@Bean注解声明bean时添加上@Qualifier注解,如下所示:
package chapter03.ambiguity;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("cold")
public class IceCream implements Dessert {
@Override
public void showName() {
System.out.println("冰激凌");
}
}
然后在注入的地方,不再使用默认生成的bean ID,而是使用刚刚指定的cold限定符:
@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
运行测试代码,输入结果如下所示:
冰激凌
此时将IceCream类重命名为Gelato,代码可以正常运行,不会受影响。
然后有一天,某位开发又新建了类Popsicle,该类也使用了cold限定符:
package chapter03.ambiguity;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("cold")
public class Popsicle implements Dessert {
@Override
public void showName() {
System.out.println("棒冰");
}
}
此时又带来了新的歧义性问题,因为Spring又不知道该如何选择了,运行代码会抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException
异常,如下所示:
此时,我们就需要用到自定义的限定符了。
3.3 自定义的限定符注解
首先,我们新建以下3个注解:
package chapter03.ambiguity;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}
package chapter03.ambiguity;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy {
}
package chapter03.ambiguity;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Fruity {
}
注意事项:这3个注解在定义时都添加了@Qualifier注解,因此它们具有了@Qualifier注解的特性
然后将IceCream类修改为:
package chapter03.ambiguity;
import org.springframework.stereotype.Component;
@Component
@Cold
@Creamy
public class IceCream implements Dessert {
@Override
public void showName() {
System.out.println("冰激凌");
}
}
将Popsicle类修改为:
package chapter03.ambiguity;
import org.springframework.stereotype.Component;
@Component
@Cold
@Fruity
public class Popsicle implements Dessert {
@Override
public void showName() {
System.out.println("棒冰");
}
}
最后,修改下注入地方的代码,使其只能匹配到1个满足条件的bean,如下所示:
@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
运行测试代码,输出结果如下所示:
冰激凌
由此,我们也可以发现,自定义注解与@Qualifier注解相比,有以下2个优点:
- 可以同时使用多个自定义注解,但@Qualifier注解只能使用1个
- 使用自定义注解比@Qualifier注解更为类型安全
4. 源码及参考
源码地址:https://github.com/zwwhnly/spring-action.git,欢迎下载。
Craig Walls 《Spring实战(第4版)》
原创不易,如果觉得文章能学到东西的话,欢迎点个赞、评个论、关个注,这是我坚持写作的最大动力。
如果有兴趣,欢迎添加我的微信:zwwhnly,等你来聊技术、职场、工作等话题(PS:我是一名奋斗在上海的程序员)。
Spring入门(八):自动装配的歧义性的更多相关文章
- Spring实战之处理自动装配的歧义性
仅有一个bean匹配所需的结果时,自动装配才是有效的.如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍Spring自动装配属性.构造器参数或方法参数.为了阐述自动装配的歧义性,假设我们使用@A ...
- spring装配---处理自动装配的歧义性
一.歧义性 当我们使用spring的注解进行自动装配bean时,如果不仅有一个bean能够匹配结果的话,会抛出NoUniqueBeanDefinitionException: 例如本例中 当sprin ...
- spring学习总结——高级装配学习二(处理自动装配的歧义性)
我们已经看到如何使用自动装配让Spring完全负责将bean引用注入到构造参数和属性中.自动装配能够提供很大的帮助.不过,spring容器中仅有一个bean匹配所需的结果时,自动装配才是有效的.如果不 ...
- Spring实战(六)自动装配的歧义性
1.Spring进行自动装配时碰到的bean歧义性问题. 在进行自动装配时,只有仅有一个bean匹配所需结果时,才是可行的. 如果不仅仅一个bean能够匹配结果,例如一个接口有多个实现,这种歧义性会阻 ...
- Spring-处理自动装配的歧义性
自动装配可以对依赖注入提供很大帮助,因为它会减少装配应用程序组件时所需的显式装配的数量. 不过,仅有一个bean匹配所需的结果时,自动装配才是有效的.如果不仅有一个bean能够匹配的话,这种歧义性会阻 ...
- SpringInAction--Bean自动装配的歧义性处理
在前面,学习如何装配Bean的时候,或许会发现,有的同类型的Bean智能配置一个 如下: package com.bean.java; import org.springframework.conte ...
- Spring处理自动装配的歧义性
1.标识首选的bean 2.使用限定符@Qualifier 首先在bean的声明上添加@Qualifier 注解: @Component @Qualifier("cdtest") ...
- Spring(六)之自动装配
一.自动装配模型 下面是自动连接模式,可以用来指示Spring容器使用自动连接进行依赖注入.您可以使用元素的autowire属性为bean定义指定autowire模式. 可以使用 byType 或者 ...
- Spring 由构造函数自动装配
Spring 由构造函数自动装配,这种模式与 byType 非常相似,但它应用于构造器参数. Spring 容器看作 beans,在 XML 配置文件中 beans 的 autowire 属性设置为 ...
随机推荐
- Linux命令学习-tail命令
Linux中,tail命令的全称就是tail,主要用于监控日志文件. 对于一个正在运行应用来说,其对应的log日志文件肯定是在不断的更新,此时,便可通过tail命令来动态显示日志文件的内容.假设当前目 ...
- Visual Studio中Es6的开发环境搭建
1.打开终端,输入初始化代码.输入代码之后会在目录中出现package.json,可以在红色下划线上写上作者名和描述(不写也可以) npm init -y 2.安装Babel转换器 npm in ...
- 【深入浅出-JVM】(6):栈帧.
代码 package com.mousycoder.mycode.happy_jvm; /** * @version 1.0 * @author: mousycoder * @date: 2019-0 ...
- P2344 奶牛抗议 离散化+前缀和+动态规划+树状数组
[题目背景] Generic Cow Protests, 2011 Feb [题目描述] 约翰家的N 头奶牛正在排队游行抗议.一些奶牛情绪激动,约翰测算下来,排在第i 位的奶牛的理智度为Ai,数字可正 ...
- Spring Boot 邮件发送的 5 种姿势!
邮件发送其实是一个非常常见的需求,用户注册,找回密码等地方,都会用到,使用 JavaSE 代码发送邮件,步骤还是挺繁琐的,Spring Boot 中对于邮件发送,提供了相关的自动化配置类,使得邮件发送 ...
- [国家集训队2012]tree(陈立杰) 题解(二分+最小生成树)
tree 时间限制: 3 Sec 内存限制: 512 MB 题目描述 给你一个无向带权连通图,每条边是黑色或白色.让你求一棵最小权的恰好有need条白色边的生成树. 题目保证有解. 输入 第一行V, ...
- TCP/IP协议-网络编程
本文转载自公众号“呆呆熊一点通”,作者:呆呆 开篇语 前两年, 就买了<TCP/IP网络编程>这本书, 由于自身基础薄弱, 只是走马观花翻阅了几张. 后来工作了这些年, 越来越感到瓶颈期已 ...
- 工作经验之石氏thinking
经常听到N多人说工作经验这个名词:也时常听到人说工作多少年就是多少年工作经验.我听着总觉得有点别扭,感觉他们把这个名词说的太简单了,而且觉得不是工作N年就一定有所谓的工作经验.我觉得归根结底还是在于工 ...
- 20131209-数据库导入导出数据-sqlhelper-第十七天
[1] 导出数据 namespace _05导出数据 { class Program { static void Main(string[] args) { string str = "Da ...
- 使用MyBatis在控制台动态打印执行的sql语句
使用MyBatis进行数据库操作的时候,sql语句都是写在相应的mapper文件中,参数也是使用占位符取值表示的,mapper文件中看不到实时执行的完整sql,有时候sql语句错误或者参数类型不对的时 ...