每日一解系列之Spring Annotations # @Qualifier

在上一篇 Spring Annotations #@Autowired中,介绍了使用@Autowired完成自动注入的使用方式,在默认情况下,Spring Framework是根据类型来解析@Autowired所注释的类,如果在Spring的容器中存在多个同种类型的Bean时,Spring Framework将无法完成依赖注入工作。原因是Spring Framework无法完成二选一或者N选一的工作,在此情况下,需要借助@Qualifier注解来指定依赖注入的范围。


每日一解系列之Spring Annotations # @Qualifier

Spring Annotations每日一解系列文章


序言

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注解指定需要进行注入的对象。


每日一解系列之Spring Annotations # @Qualifier

IDEA 提示


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进行下载:


每日一解系列之Spring Annotations # @Qualifier

https://github.com/ramostear/spring-annotations-daily-example



分享到:


相關文章: