品Spring:SpringBoot發起bean定義註冊的“二次攻堅戰”
上一篇文章整體非常輕鬆,因為在容器啟動前,只註冊了一個bean定義,就是SpringBoot的主類。
OK,今天接著從容器的啟動入手,找出剩餘所有的bean定義的註冊過程。
具體細節肯定會頗為複雜,同樣,大家只需關注都幹了什麼,不用考慮如何幹的。
來巨集觀的看下容器的啟動過程,即refresh方法,如下圖01:
只撿重要的來說,就是四大步:
第一,準備好bean工廠(BeanFactory)。
第二,呼叫已經註冊的bean工廠後處理器(BeanFactoryPostProcessor)。
第三,註冊bean後處理器(BeanPostProcessor)。
第四,例項化所有的單例bean。
其中第二、第三引入兩個新概念,“bean工廠後處理器”和“bean後處理器”。
為了更好的理解它們,再來贅述一遍和bean相關的操作過程。
註冊bean定義 -> ① -> 例項化bean -> 依賴的裝配 -> ② -> 初始化bean -> ③ -> OK
這個就是從一開始到bean例項準備好的整個流程。其中①②③是Spring預留的三個埋點,可以在這些地方插入一些使用者程式碼,進行一些定製化。
其中①位於bean定義已經註冊好後,尚未開始生成bean例項時,此處就是用來自定義處理bean定義的。
剩餘②和③位於bean例項的初始化方法執行之前和之後,此處就是用來自定義處理bean例項的。
所以①處就對應於上述的第二,即bean工廠後處理器。②和③處就對應於上述的第三,即bean後處理器。
因此,可以看出,在容器啟動過程中,能夠和bean定義搭上關係的只有上述的第二,就是bean工廠後處理器。
它就是接下來我們的突破口。可是會有人問,從一開始到現在,明明沒有人註冊過它,為什麼這裡會呼叫它呢?
咦,這個問題問的好。不妨倒推一下,首先,肯定是註冊了這個bean工廠後處理器了,不然這裡為啥要呼叫,不然剩餘的那些bean定義是如何註冊到容器裡的?
既然我們(即使用者程式碼)沒有註冊,那一定是系統(即框架程式碼)自動註冊了。好吧,只能姑且這樣認為了。
那就找吧,肯定是隱藏在了某個地方。找啊找啊找朋友,找找找。
終於,功夫不負有心人,找到了,它隱藏在了兩個類中,就是兩個負責註冊bean定義的類。
AnnotatedBeanDefinitionReader這個類的建構函式中,如下圖02:
ClassPathBeanDefinitionScanner這個類的scan方法中,如下圖03:
我們發現它們執行的是相同的程式碼,這不就執行兩遍了嗎?哈哈,裡面做了冪等處理啦。
進到這個方法裡會發現註冊了好幾個bean,但是bean工廠處理器的只有一個,如下圖04:
它的bean名稱裡帶了個internal,說明是內部使用的,即“基礎設施”的作用,如下圖05:
這個類的名稱以ConfigurationClass開頭,表示是對標有@Configuration註解的類的全權處理。
仔細想一下,讓你在Spring家族中選擇一個最特殊、最常見的註解,任何人都會選擇@Configuration這個註解。
再仔細體會下,這個註解其實具有類似“配置”和“管理”方面的功能。可以說整個Spring都是圍繞著它構建起來的。
OK,現在即將迎來本文的核心內容,打起精神來。照例還是側重整體過程,弱化具體實現細節。
第一,取出已經註冊的bean定義,其實就是主類自己這個光桿司令。
第二,判斷它是否需要被處理,滿足的條件是:
1)標有@Configuration註解
2)標有@Component註解
3)標有@ComponentScan註解
4)標有@Import註解
5)標有@ImportResource註解
6)含有@Bean方法
只要這六個條件滿足其一,就需要被處理。
由於SpringBoot的主類上標有@SpringBootApplication註解,所以上述第1條就已滿足了。
因此主類需要被處理,這不廢話嘛,目前只註冊了它自己,必須的被處理啊。
程式設計新說注:每一個符合條件的類,都可以認為是一個源(Source,即源泉),它的作用就是向容器中貢獻bean定義。
第三,如果類上標有@Component註解,就去處理它的靜態內部(巢狀)類,如下圖06:
這其實已經是遞迴了,所以處理方式是一樣的。
第四,接著處理@PropertySource註解,它可以引入.properties檔案,會把檔案中的屬性值放入到Environment中。如下圖07:
第五,接著處理@ComponentScan註解,它會掃描指定的jar包,並從中獲取bean定義,如下圖08:
第六,然後再處理@Import註解,如下圖09:
該註解共可以引入三類內容:
1)另一個普通類,但是當作@Configuration類
2)ImportSelector介面的實現類
3)ImportBeanDefinitionRegistrar介面的實現類
其中第2、3是通過實現介面,自己寫程式碼來註冊bean定義,超級靈活,隨意掌控。
程式設計新說注:這種方式的一般典型用法是,在實現第三方框架和Spring框架整合時使用。
請看下程式碼,如下圖10:
第七,然後處理@ImportResource註解,它用於引入.xml檔案,可以使xml和註解兩種方式混合使用,如下圖11:
第八,然後再處理類中的@Bean方法,如下圖1213:
第九,然後再處理接口裡面的預設方法,且方法上有@Bean註解的,如下圖1415:
第十,最後再處理父類,如下圖16:
因為每個@Configuration類除了自身是源之外,還可以向容器貢獻其它的源,所以總體是遞迴進行的。
在進行的過程中,做好了防重複處理,所以不會出現重複註冊。
以上所有這些其實都是ConfigurationClassPostProcessor這類裡面的邏輯。
它不僅僅是一個bean工廠後處理器,還是一個專門用於註冊bean定義的後處理器。
這個類在容器啟動時會被呼叫,因此把其它類的bean定義註冊到了容器中。
>>> 品Spring系列文章 <<<
品Spring:帝國的基石
品Spring:bean定義上梁山
品Spring:實現bean定義時採用的“先進生產力”
品Spring:註解終於“成功上位”
品Spring:能工巧匠們對註解的“加持”
品Spring:SpringBoot和Spring到底有沒有本質的不同?
品Spring:負責bean定義註冊的兩個“排頭兵”
品Spring:SpringBoot輕鬆取勝bean定義註冊的“第一階段”
作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!
&n