Spring入門(八):自動裝配的歧義性

1. 什麼是自動裝配的歧義性?

在Spring中,裝配bean有如下3種方式:java

  1. 自動裝配
  2. Java配置
  3. xml配置

在這3種方式中,自動裝配爲咱們帶來了很大的便利,大大的下降了咱們須要手動裝配bean的代碼量。git

不過,自動裝配也不是萬能的,由於僅有一個bean匹配條件時,Spring才能實現自動裝配,若是出現不止1個bean匹配條件時,Spring就會不知道要裝配哪一個bean,拋出org.springframework.beans.factory.NoUniqueBeanDefinitionException異常,這就是自動裝配的歧義性。github

爲了方便理解,咱們舉個具體的例子。spring

首先,咱們新建個接口Dessert,該接口僅有1個方法showName():安全

package chapter03.ambiguity;

public interface Dessert {
    void showName();
}
複製代碼

而後定義3個該接口的實現類Cake,Cookies,IceCream:微信

package chapter03.ambiguity;

import org.springframework.stereotype.Component;

@Component
public class Cake implements Dessert {
    @Override
    public void showName() {
        System.out.println("蛋糕");
    }
}
複製代碼
package chapter03.ambiguity;

import org.springframework.stereotype.Component;

@Component
public class Cookies implements Dessert {
    @Override
    public void showName() {
        System.out.println("餅乾");
    }
}
複製代碼
package chapter03.ambiguity;

import org.springframework.stereotype.Component;

@Component
public class IceCream implements Dessert {
    @Override
    public void showName() {
        System.out.println("冰激凌");
    }
}
複製代碼

而後新建甜點店類DessertShop,該類的setDessert()方法須要裝配1個Dessert的實例bean:cookie

package chapter03.ambiguity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class DessertShop {
    private Dessert dessert;

    public Dessert getDessert() {
        return dessert;
    }

    @Autowired
    public void setDessert(Dessert dessert) {
        this.dessert = dessert;
    }

    public void showDessertName() {
        this.dessert.showName();
    }
}
複製代碼

不過如今符合裝配條件的有3個bean,它們的bean ID(默認狀況下是類名首字母小寫)分別爲cake,cookies,iceCream,Spring該自動裝配哪一個呢?ide

帶着這個疑問,咱們先新建配置類AmbiguityConfig:測試

package chapter03.ambiguity;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class AmbiguityConfig {
}
複製代碼

這個類的關鍵是添加了@ComponentScan註解,讓Spring自動掃描已經定義好的bean。ui

最後,新建類Main,在其main()方法中添加以下測試代碼:

package chapter03.ambiguity;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AmbiguityConfig.class);

        DessertShop dessertShop = context.getBean(DessertShop.class);
        dessertShop.showDessertName();

        context.close();
    }
}
複製代碼

運行代碼,發現拋出org.springframework.beans.factory.NoUniqueBeanDefinitionException異常,以下所示:

那麼如何解決自動裝配的歧義性呢?Spring提供瞭如下2種方案:

  1. 標記首選的bean
  2. 使用限定符

2. 標記首選的bean

既然如今有3個匹配條件的bean,咱們能夠經過@Primary註解標記下哪一個是首選的bean,這樣當Spring發現有不止1個匹配條件的bean時,就會選擇這個首選的bean。

好比3種甜點裏,我最喜歡吃餅乾,那麼我就把Cookies標記爲首選的bean:

package chapter03.ambiguity;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class Cookies implements Dessert {
    @Override
    public void showName() {
        System.out.println("餅乾");
    }
}
複製代碼

再次運行測試代碼,輸出結果以下所示:

餅乾

圓滿解決了歧義性的問題,不過有一天,有個同事不當心在IceCream上也添加了@Primary註解:

package chapter03.ambiguity;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class IceCream implements Dessert {
    @Override
    public void showName() {
        System.out.println("冰激凌");
    }
}
複製代碼

編譯都正常,所以都沒注意,但發佈後運行時,卻拋出以下異常:

意思就是發現了不止1個首選的bean,由於此時Spring又不知道該選擇哪一個了,也就是有了新的歧義性,因此甩鍋拋出了異常。

3. 使用限定符

3.1 基於bean ID的限定符

Spring還提供了另外一個註解@Qualifier註解來解決自動裝配的歧義性,它能夠與@Autowired或者@Inject一塊兒使用,在注入的時候指定想要注入哪一個bean。

好比,咱們把IceCream注入到setDessert()的方法參數之中:

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
    this.dessert = dessert;
}
複製代碼

這裏傳遞的iceCream指的是IceCream類默認生成的bean ID。

再次運行測試代碼,輸出結果以下所示:

冰激凌

咱們能夠發現,使用了@Qualifier註解後,咱們以前標記的@Primary註解被忽略了,也就是說,@Qualifier註解的優先級比@Primary註解的優先級高。

使用默認的限定符雖然解決了問題,不過可能會引入一些問題。好比我在重構代碼時,將IceCream類名修改爲了Gelato:

package chapter03.ambiguity;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class Gelato implements Dessert {
    @Override
    public void showName() {
        System.out.println("冰激凌");
    }
}
複製代碼

此時運行代碼,會發現拋出org.springframework.beans.factory.NoSuchBeanDefinitionException異常,以下所示:

這是由於IceCream重命名爲Gelato以後,bean ID由iceCream變成了gelato,但咱們注入地方的代碼仍然使用的是iceCream這個bean ID,致使沒有找到匹配條件的bean。

鑑於使用默認的限定符的這種侷限性,咱們可使用自定義的限定符來解決這個問題。

爲不影響後面代碼的測試結果,將Gelato類再改回IceCream

3.2 基於面向特性的限定符

爲了不由於修改類名而致使自動裝配失效的問題,咱們能夠在@Component或者@Bean註解聲明bean時添加上@Qualifier註解,以下所示:

package chapter03.ambiguity;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("cold")
public class IceCream implements Dessert {
    @Override
    public void showName() {
        System.out.println("冰激凌");
    }
}
複製代碼

而後在注入的地方,再也不使用默認生成的bean ID,而是使用剛剛指定的cold限定符:

@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
    this.dessert = dessert;
}
複製代碼

運行測試代碼,輸入結果以下所示:

冰激凌

此時將IceCream類重命名爲Gelato,代碼能夠正常運行,不會受影響。

而後有一天,某位開發又新建了類Popsicle,該類也使用了cold限定符:

package chapter03.ambiguity;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("cold")
public class Popsicle implements Dessert {
    @Override
    public void showName() {
        System.out.println("棒冰");
    }
}
複製代碼

此時又帶來了新的歧義性問題,由於Spring又不知道該如何選擇了,運行代碼會拋出org.springframework.beans.factory.NoUniqueBeanDefinitionException異常,以下所示:

此時,咱們就須要用到自定義的限定符了。

3.3 自定義的限定符註解

首先,咱們新建如下3個註解:

package chapter03.ambiguity;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}
複製代碼
package chapter03.ambiguity;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy {
}
複製代碼
package chapter03.ambiguity;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Fruity {
}
複製代碼

注意事項:這3個註解在定義時都添加了@Qualifier註解,所以它們具備了@Qualifier註解的特性

而後將IceCream類修改成:

package chapter03.ambiguity;

import org.springframework.stereotype.Component;

@Component
@Cold
@Creamy
public class IceCream implements Dessert {
    @Override
    public void showName() {
        System.out.println("冰激凌");
    }
}
複製代碼

將Popsicle類修改成:

package chapter03.ambiguity;

import org.springframework.stereotype.Component;

@Component
@Cold
@Fruity
public class Popsicle implements Dessert {
    @Override
    public void showName() {
        System.out.println("棒冰");
    }
}
複製代碼

最後,修改下注入地方的代碼,使其只能匹配到1個知足條件的bean,以下所示:

@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) {
    this.dessert = dessert;
}
複製代碼

運行測試代碼,輸出結果以下所示:

冰激凌

由此,咱們也能夠發現,自定義註解與@Qualifier註解相比,有如下2個優勢:

  1. 能夠同時使用多個自定義註解,但@Qualifier註解只能使用1個
  2. 使用自定義註解比@Qualifier註解更爲類型安全

4. 源碼及參考

源碼地址:github.com/zwwhnly/spr…,歡迎下載。

Craig Walls 《Spring實戰(第4版)》

最後,歡迎關注個人微信公衆號:「申城異鄉人」,全部博客會同步更新。

相關文章
相關標籤/搜索