上一篇文章整體非常輕松,因為在容器啟動前,只注冊了一個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:SpringBoot和Spring到底有沒有本質的不同?
品Spring:SpringBoot輕松取勝bean定義注冊的“第一階段”
作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!