在上一篇 Spring Annotations #@Autowired中,介绍了使用@Autowired完成自动注入的使用方式,在默认情况下,Spring Framework是根据类型来解析@Autowired所注释的类,如果在Spring的容器中存在多个同种类型的Bean时,Spring Framework将无法完成依赖注入工作。原因是Spring Framework无法完成二选一或者N选一的工作,在此情况下,需要借助@Qualifier注解来指定依赖注入的范围。
序言
Qualifier[ˈkwäləˌfīər]本身有预选,筛选的意思,其与@Autowired注解一起使用,当需要执行依赖注入时,@Qualifier会限定Spring Framework需要选择注入的对象。这是一个很有用的注解,例如,当项目中一个Service接口有多个实现类时,就可以使用@Qualifier对依赖注入过程进行更详细的控制。
1.@Qualifier与@Autowired
当你的应用程序中有多个同种类型的Bean存在时,永远不要让Spring Framework自己决策在依赖注入的过程中选择哪一个Bean,如果你这样做了,将会收到来自Spring Framework的友情提示(臣妾做不到):
<code>Cause by : org.springframework.beans.factory.NoUniqueBeanDefinitionException/<code>
假定这样一个场景,定义一个Animal接口并包含一个speak()方法,然后定义三个类Dog,Cat和Cow实现Animal中定义的speak()方法。首先,只使用@Autowired注解并从Spring容器中获取Animal看会发生什么情况。代码如下所示:
1.1 Just Autowired Annotation
首先,需要开启Spring Framework组件扫描功能:
<code>@Configuration
@ComponentScan(basePackages = {"com.ramostear.qualifier.annotation.beans"})
public class AppConfig {
}/<code>
接着,开始创建Animal接口和其他动物类:
<code>public interface Animal {
void speak();
}/<code>
<code>@Component("cat")
public class Cat implements Animal {
@Override
public void speak() {
System.out.println("Meow ~ Meow ~");
}
}/<code>
<code>@Component("cow")
public class Cow implements Animal {
@Override
public void speak() {
System.out.println("Moo~Moo~");
}
}/<code>
<code>@Component("dog")
public class Dog implements Animal{
@Override
public void speak() {
System.out.println("Wang~Wang~");
}
}/<code>
接下来,创建一个动物园类Zoo,将这些动物都放到动物园里进行管理:
<code>@Component
public class Zoo {
@Autowired
private Animal animal;
public void animalSpeaking(){
animal.speak();
}
}/<code>
最后,在main()方法中编写代码测试并观察控制台输出:
<code>@SpringBootApplication
public class QualifierAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(QualifierAnnotationApplication.class, args);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
Zoo zoo = context.getBean(Zoo.class);
zoo.animalSpeaking();
context.close();
}
}/<code>
运行上述的main()方法,控制台将输出如下信息:
<code>***************************
APPLICATION FAILED TO START
***************************
Description:
Field animal in com.ramostear.qualifier.annotation.beans.Zoo required a single bean, but 3 were found:
- cat: defined in...
- cow: defined in...
- dog: defined in...
Action:
....or using @Qualifier to identify the bean that should be consumed/<code>
补充
这就是文章一开始提到的来自Spring Framewok的善意提示(臣妾做不到!)
如果你使用IntelliJ IDEA这类的代码编辑器,在创建Zoo类时就会提示有多个类型相同的Bean存在,Spring无法自动完成依赖注入,并推荐你使用Qualifier注解指定需要进行注入的对象。
1.2 With Qualifier Annotation
接着上一小节的案例,分别创建DogZoo,CatZoo和CowZoo三个类,并使用@Qualifier指定需要注入的对象实例:
<code>@Component
public class CatZoo {
@Autowired
@Qualifier(value = "cat")
private Animal animal;
public void animalSpeaking(){
animal.speak();
}
}/<code>
<code>@Component
public class CowZoo {
@Autowired
@Qualifier(value = "cow")
private Animal animal;
public void animalSpeaking(){
animal.speak();
}
}/<code>
<code>@Component
public class DogZoo {
@Autowired
@Qualifier(value = "dog")
private Animal animal;
public void animalSpeaking(){
animal.speak();
}
}/<code>
这样,相当于将原有的动物园进行了区域划分,不同的区域管理不同的动物,接下来,在main()方法中编写代码测试,并在控制台中观察输出结果:
<code>@SpringBootApplication
public class QualifierAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(QualifierAnnotationApplication.class, args);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
DogZoo dogZoo = context.getBean(DogZoo.class);
dogZoo.animalSpeaking();
CatZoo catZoo = context.getBean(CatZoo.class);
catZoo.animalSpeaking();
CowZoo cowZoo = context.getBean(CowZoo.class);
cowZoo.animalSpeaking();
context.close();
}
}/<code>
<code>Wang ~ Wang ~
Meow ~ Meow ~
Moo ~ Moo ~
Process finished with exit code 0/<code>
2. 自定义Qualifier注解
除了使用Spring Framework提供的@Qualifier注解之外,还可以在@Qualifier的基础上自定义qualifier注解类。在自定义qualifier注解之前,先了解@Qualifier注解类的相关细节:
<code>@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}/<code>
Spring Framework提供的Qualifier类很简单,只有一个用于表示Spring容器中bean名称的默认值value,因此,自定义qualifier类只需要使用@Qualifier注解进行注释即可。接下来,定义一个用于表示动物类型的qualifier注解AnimalType:
<code>@Qualifier
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnimalType {
String value() default "";
}/<code>
为了与上一节区分开,再定义两个Animal接口的实现类Monkey和Pig,并使用自定义的AnimalType注解进行注释:
<code>@Component
@AnimalType(value = "monkey")
public class Monkey implements Animal{
@Override
public void speak() {
System.out.println("dai~ dai~");
}
}/<code>
<code>@Component
@AnimalType(value = "pig")
public class Pig implements Animal{
@Override
public void speak() {
System.out.println("heng ~ heng ~");
}
}/<code>
接下来,定义两个类MonkeyZoo和PigZoo,并使用@AnimalType注解限定各自所依赖的对象:
<code>@Component
public class MonkeyZoo {
@Autowired
@AnimalType(value = "monkey")
private Animal animal;
public void monkeyShowTime(){
animal.speak();
}
}/<code>
<code>@Component
public class PigZoo {
@Autowired
@AnimalType(value = "pig")
private Animal animal;
public void pigShowTime(){
animal.speak();
}
}/<code>
同样,在main()方法中编写代码测试上述两个类是否能够分别将Animal注入进来,并观察控制台输出:
<code>@SpringBootApplication
public class QualifierAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(QualifierAnnotationApplication.class, args);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
MonkeyZoo monkeyZoo = context.getBean(MonkeyZoo.class);
monkeyZoo.monkeyShowTime();
PigZoo pigZoo = context.getBean(PigZoo.class);
pigZoo.pigShowTime();
context.close();
}
}/<code>
<code>dai~ dai~
heng ~ heng ~/<code>
在此小节中,演示了如何自定义并使用qualifier注解,除了将自定义qualifier注解作用于属性上,还可以将其作用于方法,对象类型以及方法参数上。
3. 根据Bean Name完成自动注入
除上述的方法之外,Spring Framework也提供了基于Bean Name完成依赖注入工作。使用此方式的前提是在类中所依赖的Bean name需要和Spring容器中所管理的Bean name保持一致。如果在使用@Component注解时指定了Bean的名称,则在引用的属性名称必须于@Component注解指定的一致。
<code>@Component
public class OtherZoo {
@Autowired
private Animal monkey;
public void showTime(){
monkey.speak();
}
}/<code>
<code>@SpringBootApplication
public class QualifierAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(QualifierAnnotationApplication.class, args);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
OtherZoo otherZoo = context.getBean(OtherZoo.class);
otherZoo.showTime();
context.close();
}/<code>
<code>dai~ dai~/<code>
综上所述,当应用程序中出现多个同种类型的Bean时,为了让Spring能够顺利完成依赖注入,使用@Qualifier注解是一个不错的选择,使用@Qualifier对依赖注入过程进行更详细的控制,防止出现二选一,多选一的情况发生。此外,文中还列举了自定义qualifier注解的定义和使用方法,以及基于Bean name完成依赖注入的演示案例。
本文所涉及的源代码已上传到Github,需要的小伙伴可点击下面的链接移步到Github进行下载:
閱讀更多 ramostear 的文章