在上一篇 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 的文章