1. 程式人生 > >Spring IOC的核心機制:例項化與注入

Spring IOC的核心機制:例項化與注入

上文我們介紹了IOC和DI,IOC是一種設計模式,DI是它的具體實現,有很多的框架都有這樣的實現,本文主要以spring框架的實現,來看具體的注入實現邏輯。 # spring是如何將物件加入容器的 spring將物件加入容器的方式有很多種,最主要的是xml和註解的形式,而當下註解的形式應用更加的廣泛,所以這裡我們也主要介紹註解注入模式下的相關知識點。 spring下的註解也是有很多種的,其中應用最為廣泛的就是模式註解。 ## 模式註解 stereotype annotations 咋一看,這是啥,說實話我也不太清楚,官方說的就叫這哈,其實他說的就是平時最常用的那些 `@Component @Service @Controller @Repository @Configuration ` 註解。 如果一個類被打上 `@Component`註解,那他就會被容器掃描到並加入到容器裡去。那這裡我們用spring的方式再來實現一下上篇文章的獲取英雄的需求。 先給 `Diana`這個英雄打上一個 `@Component`註解: ```java @Component public class Diana{ private String skillName = "Diana R"; public Diana() { } public void q(){ System.out.println("Diana Q"); } public void w(){ System.out.println("Diana W"); } public void e(){ System.out.println("Diana E"); } public void r(){ System.out.println(this.skillName); } } ``` 假設現在有一個Controller需要用到 `Diana` ```java @RestController public class BannerController { @Autowired private Diana diana; @RequestMapping(value = "/v2/banner", method = {RequestMethod.GET}) public String test() { diana.r(); return "Hello SpringBoot"; } } ``` `@Autowired`的意思就是說在這裡注入一個物件,然後我們啟動應用, 通過瀏覽器訪問 `http://localhost:8081/v2/banner`,會發現在控制檯會輸出 `Diana`的r方法的列印內容,說明這裡確實拿到了 `Diana`這個物件。 # 物件的注入時機 如果我們把 `Diana`的 `@Component`註解去掉,其他地方不變,再執行程式會發生什麼,試試看。 重新啟動的時候會提示我們下面的錯誤: ![file](https://img2020.cnblogs.com/other/1960827/202005/1960827-20200504225856487-1319850683.png) 看錯誤提示,說程式沒有找到對應的bean,就是說我這裡要注入bean了,但是在容器裡沒有找到。 這裡就會得出一個結論:**容器在初始化後就會給相應的程式碼片段進行物件的注入**。 從提示資訊中可以看到Autowired(required=true),這裡設定的預設是true,表示注入的時候,該物件必須存在,否則就會注入失敗。當然這裡可設定不報錯,Autowired(required=false),這個時候再啟動就不會報錯了,但是在訪問的時候會報空指標異常。 那這裡spring的容器究竟是在什麼時候例項化的物件呢,是在訪問controller的時候呢,還是在容器啟動的時候呢?我們可以通過下面的手段檢測一下,在 `Diana`的無參構造方法中放一個列印語句,然後再啟動程式: ```java public Diana() { System.out.println("I am Diana"); } ``` 程式啟動以後,會看到在控制檯會顯示出構造方法中列印的語句 ![file](https://img2020.cnblogs.com/other/1960827/202005/1960827-20200504225858470-1519775606.png) 這就說明在容器啟動後把類載入到容器以後,就會例項化一個物件出來。這就又得出一個結論:**容器初始化以後就會對bean進行例項化** # 物件的延遲注入 上面提到容器在確定後就會把物件(也就是bean)例項化,那有沒有辦法讓他不馬上例項化? spring提供了@Lazy這個註解,用以表明某個bean可以延遲載入,用之前的 `Diana`延時一下,給他加上@Lazy這個註解: ```java @Component @Lazy public class Diana{ private String skillName = "Diana R"; public Diana() { System.out.println("I am Diana"); } public void q(){ System.out.println("Diana Q"); } public void w(){ System.out.println("Diana W"); } public void e(){ System.out.println("Diana E"); } public void r(){ System.out.println(this.skillName); } } ``` 再次啟動程式,會發現在控制檯還是會執行構造方法裡的列印語句,這是什麼情況,不是明明已經加了延遲載入了麼? 這裡會設計到spring的一個機制,就是說如果某個bean沒有設定延遲載入,這個bean會在容器啟動就例項化,並且這個bean裡面所依賴的其他bean都會進行過例項化,即使設定了懶載入。 我們在BannerController裡用到了 `Diana`這個bean,並把它設定了延遲載入,但是並沒有把BannerController也設定成延遲載入,所以容器再例項化BannerController的時候同樣會 `Diana`這個bean進行例項化。如果要真正做到延遲載入,需要讓BannerController也要延遲載入。給它加上@Lazy以後,再啟動看一下: ![file](https://img2020.cnblogs.com/other/1960827/202005/1960827-20200504225859507-1519722427.png) 這個時候控制就沒有那條列印語句的輸出內容了,此時我們通過瀏覽器來訪問BannerController的路由,此時就會在控制檯輸出構造方法裡列印語句的輸出內容了。 # 物件的注入邏輯 上文 `Diana`的就是一個單獨的類,沒有實現任何介面,這種實現方式他是有問題的,很難進行擴充套件,這裡不應該一類具體的實現類,而是要應該依賴抽象,這樣才能滿足開閉原則,讓程式具有良好的擴充套件性。 這裡讓 `Diana`實現了一 `Skill`介面, ```java public interface Skill { void q(); void w(); void e(); void r(); } ``` 在BannerController注入這裡也要改一下: ```java @Autowired private Skill diana; ``` 執行程式,發先能夠執行成功,如果新新增一個英雄的實現類: ```java @Component public class Irelia implements Skill { public Irelia() { System.out.println("Hello, Irelia"); } public void q(){ System.out.println("Irelia Q"); } public void w(){ System.out.println("Irelia W"); } public void e(){ System.out.println("Irelia E"); } public void r(){ System.out.println("Irelia R"); } } ``` 我們再去執行程式,發現還是能執行成功,並且發現注入的是 `Diana`而不是 `Irelia`,那這裡我們可能會問為什麼呀,前面好像也沒指定注入哪個實現類。 難道是因為 `private Skill diana;`這裡寫了 `diana`麼?你還別說,還真是這個原因,這裡涉及到spring的注入機制了。 當spring的IOC容器注入bean的時候,如果發現有多個相同型別的bean時,就會去看它們的名稱(name),如果名稱符合要求,就會注入這個名稱的bean。每個被容器掃描到的bean都會有一個預設的name,就是它的類名,首字母會小寫,比如我們例項中的:BannerController的name就是bannerController,Irelia就是irelia。 可以試著把 `private Skill diana`改成 `private Skill skill`,再次執行,就會發生報錯: ![file](https://img2020.cnblogs.com/other/1960827/202005/1960827-20200504225859891-1619608050.png) 提示你找不到響應的bean。這裡要明確一點哈,雖然報錯了,但是這種寫法是標準的,上面那個直接寫實現類的名字不是標準的寫法。 那怎麼解決這個找不到的問題呢? spring提供了一個@Qualifier的註解,給他傳入要注入bean的名字,就可以指定注入的bean,@Qualifier("irelia")。 ## 總結 Autowired的注入方式有兩種,一種是按照型別注入,一種是按照名稱注入,預設是按照型別注入,如果只有一個實現類 `Diana`,這裡寫成 `private Skill skill`,執行程式,你會發現是能夠注入成功的。因為Skill型別下只有一個實現類,自然就是注入它了。如果有多個實現類,那就會先按照這個型別查詢,這個型別下所有到的實現類再按照名稱查詢,直至找到符合要求的,找不到就報錯。 **更多關於spring IOC內容,請檢視(部落格網站)[https://www.immortalp.com]** > 歡迎大家去 [我的部落格](https://www.immortalp.com) 瞅瞅,裡面有更多關於測試實戰的內容哦!!