目录
自动装配的歧义性怎么出现的?
产生自动装配的歧义性的原因是,出现了多个 bean 匹配所需的结果。
比如:
@Autowired
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
将Dessert 注入到 setDessert 方法中。但是 Dessert 为一个接口,接口被三个类实现了,分别为:
@Component
public class Cake implements Dessert { }
@Component
public class Cookies implements Dessert { }
@Component
public class IceCream implements Dessert { }
因为这三个类 都带有@Component 注解,也就是在组件扫描的时候,能够发现他们并将其创建为 Spring 应用上下文里面的 bean。然后,当Spring 试图自动装配setDessert()中的Dessert参数时,它并没有唯一、无歧义的可选值。此时会抛出NoUniqueBeanDefinitionException>
如何处理自动装配的歧义性?
解决歧义性有以下几种方法:
- 在可选 bean 中 某一个设为首选项(primary)
- 使用限定符(qualify)将可选的 bean 范围缩小到只有一个 bean
首选 bean
在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。
/**
* ①、@Primary 能够与 @Component 组合用在组件扫描的 bean 上。
*/
@Component
@Primary
public class IceCream implements Dessert { }
/**
* ②、也可以与 @Bean 组合用在 Java 配置的 bean 声明中。
*/
@Bean
@Primary
public Dessert iceCream(){
return new iceCream();
}
<!-- ③、XML 配置中,实现同样的功能 -->
<bean id="iceCream" class="com.desserteater.IceCream" primary="true"/>
不管你采用什么方式来标示首选bean,效果都是一样的,都是告诉Spring在遇到歧义性的时候要选择首选的bean。
注意:首选bean是唯一的,首选首选肯定只有一个。
限定自动装配的bean
之所以有限定自动装配 bean,是因为 设置首选 bean 时,无法 通过@Primary 注解来实现 范围限定到唯一一个无歧义性的选项。你只能选择 其中一个 bean,然而选择不了某一个无歧义性。
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。 例如,我们想要确保要将IceCream注入到setDessert()之中:
/**
* 想要确保要将 IceCream 注入到 setDessert()之中。
* 这是使用限定符的最简单的例子。
* @param dessert
*/
@Autowired
// 参数就是想要注入的 bean 的ID。
// @Qualifier("iceCream") 所引用的 bean 要具有 String 类型的"iceCream"作为限定符,
// 所有的 bean 都会给定一个默认的限定符,这个限定符与 bean 的 ID 相同。
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
为@Qualifier注解所设置的参数就是想要注入的bean的ID。setDessert()方法上所指定的限定符与要注入的bean的名称是紧耦合的。对类名称的任意改动都会导致限定符失效。
如何创建自定义的限定符
很简单只要在@Component注解的 bean 上面,同样增加 @Qualifier 注解即可,只要这两个注解里面所赋值 一一对应既可,如:
/**
* cold 限定符分配给了 IceCream bean
* 此时,没有耦合类名,可以随意重构 IceCream 的类名,而不用担心会破坏自动装配。
*/
@Component
@Qualifier("cold")
public class IceCream implements Dessert {
}
@Autowired
// 注入的地方,引用 code 限定符即可。
@Qualifier("cole")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
// 当通过 Java 配置显示定义 bean 时,也可以与 @Bean 注解一起使用
@Bean
@Qualifier("cold")
public Dessert iceCream(){
return new IceCream();
}
使用自定义的限定符注解
问题描述
自定义的限定符要比基于 bean id 的限定符好一定,毕竟可以自定义。但是 即使自定义了,也会出现 多个 bean 使用了相同的自定义限定符。
如果新建一个 bean,再次使用,上一段的 @Qualifier("cold") 这样 我们再次遇到了 歧义性的问题。
解决方法 1(错误方法):在注入点和 bean 定义的地方在新加一个 @Qualifier(" xxx")注解
此时的 冰激凌:
@Component
@Qualifier("cold")
@Qualifier("creamy")
public class IceCream implements Dessert {
}
冷加奶油也是可以做到的,同样在注入点,我们可能会使用这样的方式来将范围缩小到IceCream:
@Autowired
// 注入的地方,引用 cold 限定符即可。在家一个 creamy
@Qualifier("cold")
@Qualifier("creamy")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
理论上觉着可以,但是 java 不认同:Java不允许在同一个条目上重复出现相同类型的多个注解。编译器会提示错误!!!
解决方法 2(重构接口Qualifier):既然不能使用多个 注解,那只能自己写注解
看下 Qualifier 接口源码:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
然后模仿写个 interface:
/**
* 因为 Java 不允许在同一条目上重复出现相同类型的多个注解
* 所以使用自定义的 @Cold 来替换 @Qualifier("cold")
* 它们具有了 @Qualifier 注解的特性。本身实际上就成为了限定符注解。
*/
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}
通过在定义时添加@Qualifier注解,它们就具有 了@Qualifier注解的特性。它们本身实际上就成为了限定符注解。
// 使用必要的限定符注解进行任意组合,从而将可选范围缩小到只有一个 bean 满足需求。
@Autowired
@Cold
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有Java编译器的限制或错误。
与此同时,相对于使用原始 的@Qualifier并借助String类型来指定限定符,自定义的注解也更为类型安全。
总结
在本节和前面的节中,我们讨论了几种通过自定义注解扩展Spring的方式。
为了创建自定义的条件化注解,我们创建一个新的注解并在这个注解上添加了@Conditional。
为了创建自定义的限定符注解,我们创建一个新的注解并在这个注解上添加了@Qualifier。
这种技术可以用到很多的Spring注解中,从而能够将它们组合在一起形成特定目标的自定义注解。