品Spring:SpringBoot輕鬆取勝bean定義註冊的「第一階段」
- 2019 年 10 月 3 日
- 筆記
上一篇文章強調了bean定義註冊佔Spring應用的半壁江山。而且詳細介紹了兩個重量級的註冊bean定義的類。
今天就以SpringBoot為例,來看看整個SpringBoot應用的bean定義是如何註冊進容器的。
先來看看經典的啟動入口,如下圖01:
可以看到調用的是run方法,並把主類(main或primary)作為第一個參數出入。
接下來要做的事情,就是順藤摸瓜,看看到底發生了什麼,並確定下究竟哪些類被註冊了bean定義。
此時,我就是一個快樂的小偵探,OK,走起。
上面的調用走到了這裡,如下圖02:
可以看到把第一個參數(即主類)放入數組裡,又調用了一個run方法,如下圖03:
使用第一個參數(即主類)去調用了構造函數,得到了實例對象,然後又調用了實例的run方法。
順着構造函數走下去,最終走到了這裡,如圖04:
發現最終主類被,放到Set<Class<?>>類型的primarySources字段中。
編程新說注:通過搜索全類,發現這個字段除了剛剛放入的主類外,再沒有放入其它類。
接着再沿着run方法往下走,來到了這裡,如下圖05:
首先定義了一個容器類的變量,然後創建容器類的實例,就是通過反射調用構造函數了。
然後就是準備容器,進入方法里看看,如下圖06:
在方法最後終於看到了我們期望的,即bean定義的註冊。
發現要註冊的資源是getAllSources()這個方法返回的,那就進去看看吧,如下圖07:
看到資源來自於primarySources字段和sources字段。第一個字段上文已經講了,只包含主類。
編程新說注:通過搜索全類,發現第二個字段sources是null,因此它不包含資源。
因此,真正獲取到的用於註冊bean定義的資源只有主類自己。
那就打破砂鍋走到底,繼續吧。
再來看看load方法,如下圖08:
使用剛剛獲取到的資源創建了BeanDefinitionLoader類的實例。
這個類是SpringBoot定義的,類似於一個門面,因為它包含了所有註冊bean定義的方式。
這個類就是最後一步了,因此來看看,如下圖09:
首先是一個Object[]類型(之所以用Object,是因為資源類型有多種)的sources字段,用於存儲剛剛獲取的資源。
剩下四個都是用來註冊bean定義的,其中兩個上一篇已經講過。剩餘兩個是處理xml和groovy的,一個已經過時,一個尚未流行。
最後再來看一眼,生成實例時調用的構造函數,如下圖10:
就是對五個字段的賦值或實例化,並無特別之處。(其實是有的,先賣個關子)
接下來就是根據資源的具體類型,使用四個bean定義註冊類中的一個來註冊bean定義。
這一通分析下來,推導出來的結論是:
截止到目前,只有主類自己被註冊了bean定義。
為了證明這一點,把日誌級別改為DEBUG,如下圖11:
可以看出在源碼中,把資源數組進行了debug輸出。
最終輸出內容,如下圖12:
發現確實只註冊了主類自己,沒有其它。和我們分析的一樣,哈哈。
到現在prepareContext已經執行完畢了,接下來該執行的就是refreshContext了。
熟悉Spring容器的都知道,refresh其實就是容器的啟動了。
因此最後得出一個結論,對於“常規”的SpringBoot應用:
在Spring容器啟動前,只有應用的主類自己被註冊了bean定義。
What,are you kidding me?
Of course not。
那其它的那些bean定義是何時及如何註冊的呢?
且聽下回分解。
最後來看看主類的bean定義信息,作為一個小小的彩蛋吧。
如下圖13:
可以看出bean名稱符合生成規則,bean定義使用了CGLIB生成了代理。
bean的一些屬性,單例、非抽象、非延遲加載、未明確定義自動裝配方式、作為自動裝配候選bean,非主要的等等。
bean定義的實現類是AnnotatedGenericBeanDefinition,可知是通過編程方式(而非jar包掃描)註冊的bean定義。
預祝,看過本文的人都有所收穫。若能轉發一下,則求之不得。
>>> 品Spring系列文章 <<<
品Spring:SpringBoot和Spring到底有沒有本質的不同?
作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!