1. 程式人生 > >品Spring:SpringBoot發起bean定義註冊的“二次攻堅戰”

品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