哈囉大家好,我是古古。
在上一篇文章中,我們有介紹了 IoC、DI、和 Bean 的概念,先帶大家了解 Spring IoC 中的重要名詞的含義。
那麼這篇文章,我們就會實際到 IntelliJ 中,練習要如何在 Spring Boot 程式中創建 Bean,以及要如何將 Bean 去注入到別的 class 中。
在 Spring Boot 中,最常見的創建 Bean 的方法,就是在 class 上面加上一行 @Component
的程式。只要在 class 上面加上一行 @Component
之後,就可以將這樣 class 變成一個 Bean 了,Magic!
所以只要我們將 HpPrinter 改寫成是下面這個樣子(注意在最上面加了一行 @Componenet
),就可以將 HpPrinter 變成一個由 Spring 容器所管理的 Bean 了。
因此當 Spring Boot 程式運行起來之後,Spring Boot 就會去查看有哪些 class 上面加上了 @Component
,然後 Spring Boot 就會提前去 new 一個 object 出來,並且存放在 Spring 容器裡面,等著其他人後續來跟他借。
所以以 HpPrinter 這個例子來說,當 Spring Boot 看到 HpPrinter 上面有加上 @Component
之後,Spring Boot 就會去執行下面這行程式,提前去 new 出一個 HpPrinter 的 object 出來。
Printer hpPrinter = new HpPrinter();
並且 Spring Boot 會將 hpPrinter 這個 object,存放在 Spring 容器中,等著後續其他人來借,因此架構就會長得像是下圖這樣(即是在 spring 容器中存放了一個 hpPrinter 的 object):
也因為使用 @Component
來創建 Bean 可以說是非常的神速,只需要在 class 上面加上一行程式就可以完成,因此 @Component
也可以說是在 Spring Boot 中使用頻率最高的註解之一。
在使用 @Component
來創建 Bean 時,有一個重點要特別提一下,就是這些被創建出來的 Bean,他們的名字,會是「class 名稱的第一個字母轉成小寫」。
舉例來說,當我們在 HpPrinter class 上面加上一個 @Component
之後,那麼 Spring 所生成出來的 Bean 的名字,就會是 hpPrinter。
所以同樣的道理,假設我們今天改成是將 @Component
加在 CanonPrinter class 上,那 Spring 所生成出來的 Bean,名字就會是 canonPrinter。
因此大家之後在創建 Bean 時,就要記得 「Spring 所生成出來的 Bean 的名字,會是 class 名稱的第一個字母轉成小寫」,這個特性在下一篇文章中馬上就會用到,所以也是一個很重要的特性!
補充:除了在 Class 上面加上
@Component
可以創建 Bean 之外,其實也是可以使用另一種方式@Bean
+@Configuration
,去創建一個 Bean 出來的。不過因為這部分相對比較複雜,因此在本系列文中不會特別介紹到@Bean
+@Configuration
的實作。
了解了創建 Bean 的方法之後,接著我們可以來看一下如何去「注入 Bean」。
要注入 Bean 也很簡單,只需要在變數上面加上 @Autowired
這行程式,就可以將 Spring 容器中的 Bean 給注入進來了,Magic again!!
不過,在使用 @Autowired
去注入 Bean 進來時,有兩個很重要的限制一定得遵守!如果不遵守的話,是沒辦法正常去注入 Bean 進來的。
當某個 class 想要使用 @Autowired
去注入一個 Bean 進來時,這個 class 自己本身也得變成是由 Spring 容器所管理的 Bean 才可以,因為這樣子 Spring 容器才有辦法透過 DI(依賴注入),將我們想要的 Bean 給注入進來。
所以假設我們是一個 Teacher,然後我們想要使用 @Autowired
,把 HpPrinter 這個 Bean 給注入進來的話,那麼 Teacher 本身也必須成為一個 Bean 才可以。
因此這時候,我們就必須先在 Teacher class 上面,先去加上一行 @Component
,將 Teacher 先變成是一個 Bean,這樣後續才可以將透過 @Autowired
,將 HpPrinter 這個 Bean 給注入到 Teacher 裡面。
補充:因此基本上當我們使用了 Spring Boot 這套框架之後,我們就會盡量把所有的 class 都變成 Bean,因為這樣才能夠去注入來注入去的,所以在 Spring Boot 程式裡面看到一堆
@Component
和@Autowired
是很正常的!
使用 @Autowired
的第二個注意事項,即是 @Autowired
是根據 「變數的類型」來尋找 Bean。
當我們在 Teacher class 中的 printer 變數上面,加上了一行 @Autowired
的程式之後,其實就表示「我們想要注入一個 Printer 類型的 Bean」,因此 Spring 容器就會查看他裡面有沒有 Printer 類型的 Bean,如果有的話,Spring 容器就會把這個 Bean 注入給 Teacher,如果沒有的話,就會出現錯誤並且停止程式。
而 HpPrinter class 之所以能夠成功的被注入到 Printer 類型的變數裡面,原因就在於 Java 的多型(polymorphism)特性,使得 HpPrinter class 可以「向上轉型」成 Printer interface,因此透過 Java 的多型,Spring Boot 就能夠成功的將 hpPrinter Bean,注入到 Teacher class 中的 printer 變數了!
補充:因為這裡牽涉到 Java 的多型特性,因此不熟悉這部分的話,建議要先回頭了解一下 Java 的多型特性會比較好,多型的概念會貫穿整個 Spring Boot 開發,因此這部分建議大家要了解一下會比較好。
所以總結來說,如果想要使用 @Autowired
去注入一個 Bean 時,必須滿足:
@Component
)。@Autowired
是透過 「變數的類型」 來注入 Bean 的(所以只要 Spring 容器中沒有那個類型的 Bean,就會注入失敗)。只要記住以上兩點,大家就可以在想要的地方使用 @Autowired
,去注入 Spring 容器中的 Bean 進來了!
了解了 @Component
和 @Autowired
的概念之後,接著我們也可以實的到 Spring Boot 中,來練習他們的用法。
在前面的 Day 4 - 第一個 Spring Boot 程式 中,我們有去創建了一個 MyController class 出來,所以現在在 Spring Boot 的程式裡面,就會存在兩個 class,分別是 DemoApplication 以及 MyController。
接著我們在 com.example.demo
這個 package 底下,再去創建一個 Printer interface 出來。所以我們可以在 com.example.demo
這個 package 上點擊右鍵,然後選擇 New,接著選擇 Java class。
然後我們將這個 interface 的名字,取名成 Printer,並且在下面選擇 interface,這樣子就可以去創建一個 Printer interface 出來。
接著在這個 Printer 的 interface 裡面,新增一個 print()
方法的宣告。
public interface Printer {
void print(String message);
}
所以創建好 Printer interface 之後,整個結構會長的像是下面這個樣子:
創建好 Printer interface 之後,接著我們可以再去創建一個 class,來實作 Printer interface。
所以我們就在 com.example.demo
底下,創建一個 HpPrinter class 出來,並且在這個 HpPrinter class 中,撰寫以下的程式:
@Component
public class HpPrinter implements Printer {
@Override
public void print(String message) {
System.out.println("HP印表機: " + message);
}
}
創建好 HpPrinter class 之後,整個結構會長的像是下面這個樣子:
在這個 HpPrinter 的實作中,我們在第 5 行有添加了 @Component
的程式,又因為 @Component
的用途是「將 class 變成由 Spring 所管理的 Bean」,因此這段程式的意思就是將 HpPrinter 變成是一個 「由 Spring 容器所管理的 Bean」。
所以到時候當 Spring Boot 程式運行起來時,Spring 就會預先去創建 hpPrinter 這個 Bean 出來,並且存放在 Spring 容器裡面了。
創建好 HpPrinter 這個 Bean 之後,接著我們可以嘗試把這個 HpPrinter 這個 Bean,注入到我們想要的地方。
這裡大家可以先回到 MyController class,然後在 MyController 中,加上下圖中第 10~11 行的程式:
所以在 MyController 中,我們就新增了一個 Printer 類型的變數 printer,並且在這個 printer 變數上面,也加上了一行 @Autowired
的程式,因此到時候,Spring Boot 就會將「Spring 容器中類型為 Printer 的 Bean,注入到這個 printer 變數中」了!
所以換句話說的話,到時候 Spring Boot 就會把存放在 Spring 容器中的 hpPrinter Bean,去注入到 MyController 的 printer 變數中,因此到時候這個 printer 變數所存放的,就會是一個 HpPrinter 的 object。
而當 hpPrinter 被注入到 printer 變數中之後,我們就可以在下面的 test()
方法中,使用 printer 變數中的 print()
方法,嘗試去印出一行 Hello World 的字串出來了(如下圖中的第 15 行所示)。
所以當我們寫到這裡之後,就能夠成功的使用 @Autowired
,去注入一個 Bean 進來了!
到這邊大家可能會有一個疑問,就是「為什麼 MyController 可以使用 @Autowired
去注入 Bean?」。
因為我們在前面有提到,如果想要使用 @Autowired
的話,這個 class 本身也得是一個 Bean 才行。但是在上面的程式中,我們明明沒有在 MyController 上面加上 @Component
將他變成一個 Bean,那為什麼 MyController 可以使用 @Autowired
去注入 Bean 呢?
其實,MyController 在這裡也是有偷偷成為一個 Bean 的,而這就是 @RestController
這一行程式所造成的效果(如下圖中的第 7 行所示)。
大家可以先把 @RestController
當成是一個厲害版的 @Component
,他不僅可以將 class 變成 Bean,還有更多的功能在裡面。因此如果回到最一開始的問題「為什麼 MyController 可以使用 @Autowired
?」,原因就是因為 MyController 其實本身也是一個 Bean! 因此他才有能力去使用 @Autowired
,將想要的 Bean 給注入進來。
所以大家在使用 @Autowired
時,就一定要記得,只有當 class 本身是一個 Bean 時,才可以使用 @Autowired
去注入一個 Bean 進來,這點非常重要!!
當上述的程式都寫好之後,我們就可以回到 DemoApplication,然後點擊左側的播放鍵,去運行這個 Spring Boot 程式。
運行起來之後,當看到下方的 console 出現「Started DemoApplication in 0.658 seconds」時,就表示 Spring Boot 程式運行成功了。
接著我們可以打開 Google 瀏覽器,然後在裡面輸入 http://localhost:8080/test ,然後按下 Enter 鍵。
這時候如果頁面中有呈現「Hello World」的字樣的話,就表示請求成功了,所以我們就可以回到 IntelliJ 上來看一下結果。
這時回到 IntelliJ 上,就可以在 console 下方看到一行「HP印表機: Hello World」的輸出。
之所以會出現這一行「HP印表機: Hello World」的輸出,是因為當我們在瀏覽器輸入
http://localhost:8080/test
時,Spring Boot 就會去執行 MyController 裡面的 test()
方法,因此就會執行到下圖中第 15 行的 printer.print("Hello World")
程式,所以最終才會在 console 上印出「HP印表機: Hello World」的字串。
因此只要看到「HP印表機: Hello World」這一行出現,就表示我們成功的透過 @Component
和 @Autowired
,在 Spring Boot 中創建一個 HpPrinter Bean 出來,並且將他注入到 MyController 裡面啦!讚!
所以大家以後就可以透過這個用法,去使用 @Component
和 @Autowired
了!
這篇文章我們先介紹了要如何使用 @Component
去創建一個 Bean,也介紹了要如何使用 @Autowired
去注入一個 Bean,最後我們也有實際到 Spring Boot 練習這兩個註解的用法,讓大家了解要如何在 Spring Boot 程式中創建和注入 Bean。
那麼到這邊,可能有的人開始會有一個疑惑,也就是:既然 @Autowired
是透過「變數的類型」來注入 Bean,那假設在 Spring 容器中,同時有 2 個以上同樣類型的 Bean 該怎麼辦? 這時候 Spring 容器是會隨機挑選一個 Bean 來注入,還是會直接報錯?
所以下一篇文章,我們就會接著來介紹另一個註解 @Qualifier
的用法,介紹要如何使用 @Qualifier
,去協助 @Autowired
選擇要注入的 Bean,那我們就下一篇文章見啦!
補充:本文是擷取自我開設的線上課程 「Java 工程師必備!Spring Boot 零基礎入門」 的內容,如果你想了解更多的 Spring Boot 的用法,歡迎參考課程簡介 (輸入折扣碼「HH202504KU」即可享 85 折優惠)。