Day 8 - 指定注入的 Bean - @Qualifier

古古

2023/11/08


哈囉大家好,我是古古

在上一篇文章中,我們有介紹了如何使用 @Component 來創建 Bean,也有介紹要如何使用 @Autowired 來注入 Bean

那麼接著這篇文章,我們就會來介紹,當 Spring 容器中有 2 個以上同樣類型的 Bean 存在時,該怎麼去選擇要注入的 Bean

回顧:注入 Bean 的方法 - @Autowired

在前一篇文章中有介紹到,我們是可以在變數上加上 @Autowired,將想要的 Bean 給注入進來的

不過在使用 @Autowired 來注入 Bean 時,必須滿足以下事項:

  1. 首先必須要確保 「自己也是一個 Bean」(即是有在 class 上面加上 @Component
  2. 並且 @Autowired 是透過 「變數的類型」 來注入 Bean 的

所以在使用 @Autowired 去注入 Bean 進來時,Spring Boot 就是會透過「變數的類型」,去 Spring 容器中尋找是否有類型符合的 Bean,如果有同類型的 Bean 存在時,即可以注入成功,如果沒有 Bean 存在,則注入失敗,Spring Boot 就會報錯並且運行失敗

像是上面的例子中,因為 Spring 容器中只有 hpPrinter 這個 Bean,且 hpPrinter 又是可以向上轉型成 Printer 類型,所以 Spring Boot 就會判定他們類型符合,因此就能夠注入成功

但是,假設在 Spring 容器中,同時有兩個一樣類型的 Bean 存在,譬如說像是下面這張圖,在 Spring 容器中同時有 hpPrinter 和 canonPrinter 這兩個 Bean 存在,那麼在這個情況下,Spring Boot 會如何運作呢?

答案是:Spring Boot 會出現錯誤並且運行失敗,因為 hpPrinter 和 canonPrinter 同時都可以向上轉型成 Printer 類型,所以 Spring Boot 不知道該注入哪一個 Bean,因此錯誤原因就會是「同時有多個同樣類型的 Bean 存在,因此無法選擇要注入哪一個」

而為了解決這個問題,就是 @Qualifier 這個註解登場的時候了!

指定注入的 Bean 的名字:@Qualifier

@Qualifier 的用途,是去指定要注入的 Bean 的「名字」是什麼,進而解決同時有兩個同樣類型的 Bean 存在的問題

所以在一般的情況下,我們是可以直接使用 @Autowired 去注入 Bean 的,但是假設今天有兩個同樣類型的 Bean 存在時,那麼我們在使用 @Autowired 的時候,就必須同時去搭配 @Qualifier 這個註解,去選擇要注入的 Bean 是哪一個

所以簡單的說的話,就是先由 @Autowired 篩選類型、再由 @Qualifier 篩選名字,透過這樣子的連環組合拳來解決這個問題!

還是剛剛的例子,假設目前在 Spring 容器中有兩個 Bean:hpPrinter 和 canonPrinter,這時候如果我們想要指定要注入 hpPrinter 這個 Bean 的話,那麼我們就只要在 Printer 變數上面,再加上一個 @Qualifier,並且在裡面,去指定 要注入的 Bean 的名字 是「hpPrinter」,就可以成功去注入 hpPrinter Bean 了,可喜可賀!

1. 使用 @Qualifer 的注意事項之一:必須搭配 @Autowired 一起使用

在使用 @Qualifier 去指定「要注入的 Bean」時,一定要搭配 @Autowired 一起使用,單純使用 @Qualifier 是沒有任何用處的

其實大家也可以直接把 @Qualifier 當成是 @Autowired 小弟這樣,他只是專門在輔助 @Autowired 的,如果沒有 @Autowired 的話,他基本上什麼作用都沒有

2. 使用 @Qualifer 的注意事項之二:指定的是「Bean 的名字」

如前面所介紹的,@Qualifier 是為了解決「多個類型同時存在」的問題而誕生,因此他所指定的是 「Bean 的名字」,也由於 @Qualifier 指定的是 Bean 的名字,因此掌握 Bean 的名字的生成方式就非常的重要!

當我們平常使用 @Component 去創建 Bean 時,這些 Bean 的名字,就會是「Class 名的第一個字母轉成小寫」

所以像是由 HpPrinter class 所生成的 Bean,就會叫做 hpPrinter,由 CanonPrinter 所生成的 Bean,名字就會叫做 canonPrinter

因此大家在使用 @Qualifier 去指定要注入的 Bean 的名字時,記得第一個字母要改成小寫,這樣子才能夠正確的去注入該 Bean 進來!

補充:而有關 Bean 的名字生成機制,可以參考 Day 7 - Bean 的創建和注入 - @Component、@Autowired 的介紹

在 Spring Boot 中練習 @Qualifier 的用法

了解了 @Qualifier 的用法之後,我們也可以實際到 Spring Boot 程式中來應用 @Qualifier

延續上一篇文章的程式,目前在 Spring Boot 程式中已經有一個 Printer interface、以及一個 HpPrinter 的 class,並且在 MyController 裡面,會使用了 @Autowired 去注入一個 Printer 類型的 Bean 進來

由於目前在這個 Spring Boot 程式裡面,只有一個 hpPrinter Bean 有辦法向上轉型成 Printer 類型,因此在 MyController 這裡所注入的,就會是 hpPrinter 這個 Bean

要測試 @Qualifier 的用法的話,我們可以嘗試在 com.example.demo 這個 package 裡面,再去新增一個 CanonPrinter 的 class 出來,並且在裡面實作如下的程式:

@Component
public class CanonPrinter implements Printer {

    @Override
    public void print(String message) {
        System.out.println("Canon印表機: " + message);
    }
}

當我們這樣寫之後,等於是 HpPrinter 和 CanonPrinter 這兩個 class,到時候都會被 Spring Boot 所管理,所以 Spring Boot 到時候就會各 new 出一份 bean,並且存放在 Spring 容器中

這時候我們回到 MyController 上面看一下的話,就會發現在第 11 行的 printer 變數下面,出現了一個紅色的波浪線

這是因為 IntelliJ 能透過我們所寫的程式,預判到這裡會出現注入的問題(Spring 容器同時存在多個同類型的 Bean),因此提前出現了紅色的波浪線,提示我們此處有錯誤

如果我們不管這個紅色波浪線,執意要運行 Spring Boot 程式的話,在啟動 Spring Boot 的過程中,console 就會噴出下列的錯誤訊息,提示我們啟動 Spring Boot 失敗

在 console 的錯誤訊息中,有出現一段「MyController required a single bean, but 2 were found」,這一行的訊息就是表示「MyController 想要注入一個 Bean,但是發現 Spring 容器中存在 2 個同樣類型的 Bean,因此注入失敗」

而在下方也會有提示,要「using @Qualifier」來解決這個 multiple Bean 的問題

因此我們可以回到 MyController 上,然後在 printer 變數上面,去加上一行 @Qualifier( "canonPrinter") 的程式,指定要去注入「Bean 的名字為 canonPrinter」的那個 Bean

寫好之後可以重新運行一下 Spring Boot,當看到 console 下方出現「Started DemoApplication in 1.135 seconds」,就表示 Spring Boot 程式運行成功了

接著我們一樣是可以打開 Google 瀏覽器,然後在裡面輸入 http://localhost:8080/test ,然後按下 Enter 鍵,這時候頁面中有呈現「Hello World」的字樣的話,就表示請求成功了,我們可以接著回到 IntelliJ 軟體上來看一下結果

這時回到 IntelliJ 上,就可以在 console 的下方看到一行「Canon印表機: Hello World」

而會出現這一行「Canon印表機: Hello World」的原因是因為,我們在 MyController 中加上了第 12 行的 @Qualifier("canonPrinter"),去指定了「要注入的 Bean 為 canonPrinter 這個 Bean」,因此到時候 Spring Boot 就會將 canonPrinter 給注入到這個 printer 變數裡面

所以後續在下方第 17 行去執行 printer.print("Hello World") 時,實際上是去執行 canonPrinter 的 print 方法,因此才會在 console 上輸出「Canon印表機: Hello World」

所以只要看到「Canon印表機: Hello World」這一行的出現,也就表示我們成功的透過 @Qualifier,去指定要注入的 Bean 是哪一個了!

補充:大家也可以嘗試把第 17 行修改一下,像是改成 @Qualifier("hpPrinter"),就可以改成是去注入 hpPrinter 那個 Bean 進來,因此在 console 上輸出的,就會改成是「HP印表機: Hello World」

總結

這篇文章介紹了要如何使用 @Qualifier,去指定要注入的 Bean 的名字,進而去輔助 @Autowired 透過變數的類型去注入 Bean 的衍生問題,並且我們也實際的到 Spring Boot 中,練習了 @Qualifier 的用法

而在介紹完 Bean 的創建和注入之後,接著我們會來介紹要如何「初始化」一個 Bean,這也是在實戰中非常常用到的用法,那我們就下一篇文章見啦!

相關連結