賀!此系列文榮獲 2023 iThome 鐵人賽《優選》獎項,正在規劃出書中,感謝大家的支持🙏,同名課程「Java 工程師必備!Spring Boot 零基礎入門」也已在 Hahow 平台上架
哈囉大家好,我是古古
上一篇文章有和大家介紹了 IoC、DI、Bean 這些 Spring IoC 中的重要名詞,那麼這篇文章,我們就實際的到 IntelliJ 中,來練習要如何在 Spring Boot 程式中去創建 Bean、以及要如何將 Bean 去注入到別的 class 中
在 Spring Boot 中,最最最最常見的去創建一個 Bean 的方法,就是在 class 上面加上一行 @Component
的程式,這樣就可以了,Magic!!
所以只要我們將 HpPrinter 改寫成是下面這個樣子(注意在最上面加了一行 @Componenet
),就可以將 HpPrinter 變成是一個由 Spring 容器所管理的 Bean 了
因此當 Spring Boot 程式運行起來之後,Spring Boot 就會去查看有哪些 class 上面加上了 @Component
,然後 Spring Boot 就會去提前去 new 一個 object 出來,並且存放在 Spring 容器裡面,等著其他人後續來跟他借
以此處為例,當 Spring Boot 看到 HpPrinter 上面有加上 @Component
之後,Spring Boot 就會去執行下面這行程式,提前去 new 出一個 HpPrinter 的 object 出來
Printer hpPrinter = new HpPrinter();
並且 Spring Boot 會將 hpPrinter 這個變數,存放在 Spring 容器中,等著後續有其他人來借,因此架構就會長得像是下圖這樣
也因為使用 @Component
來創建 Bean 可以說是非常神速,只需要在 class 上面加上一行程式就可以完成,因此 @Component
也可以說是在 Spring Boot 中使用頻率最高的註解之一
在使用 @Component
來創建 Bean 時,有一個特別重要的地方要跟大家說一下,就是 這些被創建出來的 Bean,他們的名字,會是「Class 名的第一個字母轉成小寫」
舉例來說,上面的 HpPrinter class,當我們在這個 class 上面加上一個 @Component
之後,Spring 所生成出來的 Bean 的名字,就會是 hpPrinter
所以同樣的道理,假設我們今天改成是將 @Component
加在 CanonPrinter class 上面,那 Spring 所生成出來的 Bean,就會是 canonPrinter
因此大家之後在創建 Bean 時,需要記得「Spring 生成出來的 Bean 的名字,會是 Class 名的第一個字母轉成小寫」,這個特性在下一篇文章中就會馬上用到,所以也是一個很重要的特性!
補充:除了在 Class 上面加上
@Component
可以創建 Bean 之外,也是可以使用@Bean
+@Configuration
這種方式去創建 Bean 的,不過因為這部分相對複雜,因此在此系列文中不會特別介紹到
了解了創建 Bean 的方法之後,接著我們可以來看一下如何去注入 Bean
要注入 Bean 也是很簡單,只需要在變數上面加上 @Autowired
這行程式,就可以將 Spring 容器中的 Bean 給注入進來了,Magic again!!
不過,在使用 @Autowired
去注入 Bean 進來時,有兩個很重要的限制一定得遵守才行!如果不遵守的話,是沒辦法正常去注入 Bean 進來的
當我們想要使用 @Autowired
去注入一個 Bean 進來時,我們自己本身也得變成是由 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 時,是會根據 「變數的類型來尋找 Bean」
假設今天我們在 Teacher 裡面的 Printer 變數上面加上了 @Autowired
,就表示我們想要注入一個 Printer 類型的 Bean,因此 Spring 容器就會看他裡面有沒有 Printer 類型的 Bean,如果有的話,就會把這個 Bean 給注入進去,如果沒有的話,就會出現錯誤並且停止程式
而之所以 HpPrinter 這個 class,可以成功的被注入到 Printer 類型的變數裡面,原因就在於「Java 的多型 (polymorphism)」,使得 HpPrinter class 可以「向上轉型」成 Printer interface,因此就能夠成功的將 hpPrinter 這個 Bean,給注入到 Teacher 這個 class 裡面了!
補充:因為這裡牽涉到 Java 的多型特性,因此不熟悉這部分的人可以先回頭看一下 Java 的多型特性介紹,這個概念會貫穿整個 Spring Boot 開發,因此建議還是要了解一下會比較好!
那說了這麼多,這裡先來總結一下 @Autowired
的具體用法和限制
想要使用 @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
接著我們可以去創建一個 Printer interface 出來,只要在 com.example.demo 這個 package 上點擊右鍵,然後選擇 New,接著選擇 Java class
然後在下面選擇 interface,並且上面的名字輸入 Printer,這樣就可以去創建一個 Printer interface 出來
接著在這個 Printer 的 interface 裡面,去新增一個 print()
方法的宣告
public interface Printer {
void print(String message);
}
創建好 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 的第 5 行處,有添加了一個 @Component
在上面,所以這就表示說,我們將 HpPrinter 變成是一個 「由 Spring 容器所管理的 Bean」
因此到時候當 Spring Boot 運行起來時,就會預先去創建 hpPrinter 這個 Bean 出來,並且存放在 Spring 容器裡面了
創建好 HpPrinter 這個 Bean 之後,接著我們可以來嘗試把這個 HpPrinter 的 Bean,去注入到我們想要的地方
在這裡大家可以回到 MyController 這個 class,然後在 MyController 裡面,加上如下的程式:
在 MyController 這裡,我們先新增了一個 Printer 類型的變數,並且在這個變數上面加上一個 @Autowired
,這樣到時候,Spring Boot 就會將「Spring 容器中類型為 Printer 的 Bean,給注入到這個 printer 變數上面」
所以換句話說的話,到時候 Spring Boot 就是會把存放在 Spring 容器中的 hpPrinter Bean,去注入到 MyController 的 printer 變數上,因此到時候這個 printer 變數所存放的,就會是一個 HpPrinter 的 object
而當 printer 變數被注入進來之後,我們就可以在下面的 test()
方法中,去使用 printer 變數中的 print()
方法,嘗試去印出一行 Hello World 出來
當我們寫到這裡之後,使用 @Autowired
去注入一個 Bean 進來的程式也就完成了!
@Autowired
?在這裡有一個地方要補充一下,就是我們在前面有提到,使用 @Autowired
的 class,本身也得是一個 Bean 才是,但是在上面的程式中,我們明明沒有在 MyController 上面加上 @Component
將他變成 Bean,為什麼 MyController 可以使用 @Autowired
去注入 Bean?
其實 MyController 在這裡也是有偷偷成為一個 Bean 的,而這就是 @RestController
所造成的效果
大家可以先把 @RestController
當成是一個厲害版的 @Component
,他不僅可以將 class 變成 Bean,還有更多的功能在裡面,不過總結來說的話,MyController 本身在此處也是一個 Bean 的沒錯!所以他才有能力去使用 @Autowired
去注入 Bean 進來
上述的程式都寫好之後,我們就可以回到 DemoApplication 這個 class,然後點擊播放鍵,去運行這個 Spring Boot 程式
運行起來之後,當看到下方的 console 出現「Started DemoApplication in 1.313 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()
方法,因此才會執行到 printer.print("Hello World")
這行程式,所以才會在 console 上印出「HP印表機: Hello World」的字串
因此只要看到「HP印表機: Hello World」這一行出現,就表示我們成功的透過 @Component
和 @Autowired
,在 Spring Boot 中創建一個 HpPrinter Bean 出來,並且將他注入到 MyController 裡面了!
這篇文章先介紹了創建 Bean 的註解 @Component
的用法,也介紹了注入 Bean 的註解 @Autowired
的用法,最後也實際的在 Spring Boot 中練習了這兩個註解的用法,讓大家了解要如何在 Spring Boot 程式中去創建和注入 Bean
那麼到這邊可能有的人就會有疑惑了,既然 @Autowired
是透過「變數的類型」來注入 Bean,那假設在 Spring 容器中有 2 個以上同樣類型的 Bean 該怎麼辦?這時候 Spring 容器到底是會隨機挑選一個 Bean 來注入,還是會直接兩手一攤報錯?
上面的問題 Spring Boot 都想到了!因此下一篇文章就會接著來介紹另一個註解 @Qualifier
,去協助 @Autowired
選擇想要注入的 Bean,那我們就下一篇文章見啦!
講到@RestController時可以看原碼,它又有個標註@Controller
而@Controller的原碼中又有個標註@Component
所以@RestController自然也是@Component
噢噢感謝補充!確實 @RestController 就是因為自帶 @Component,所以他才能成為一個 Bean 沒錯XD(文章中因為篇幅關係,所以沒有特別展開這邊的介紹,感謝補充~)
您好,我想請問HpPrinter是因為有 implements Printer 這個interface,在變成 Bean 之後才能被視別為一種Printer,然後透過在MyController這個Class 中 @Autowired注入的嗎?
對的唷!完全正確!
這裡就是利用到了 「Java 的多型 (polymorphism)」的特性,所以才可以將 HpPrinter 的類型,轉換成 Printer 這個類型
好的了解,謝謝您的回覆!