每日一解系列之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



分享到:


相關文章: