1. 程式人生 > 其它 >Spring原始碼-學習隨筆(三)springboot的類載入beanDefinition的過程簡述

Spring原始碼-學習隨筆(三)springboot的類載入beanDefinition的過程簡述

一、

  在spring傳統專案中,我們的bean定義資訊是存放在xml中的,在專案啟動的時候,需要將xml傳遞給容器

  但是到了springboot中,普遍使用javaConfig來定義bean,使用@Component、@Configuration、@ComponentScan這些基礎註解實現的配置

  還有在自動配置中,一些按照約定注入的bean是寫在spring.factories檔案的,這些bean是通過@Import註解批量註冊到容器中的

  但是在springboot啟動的時候,我們是沒有任何顯式的操作,這些bean就會自動註冊到容器中了,那麼接下來就找找這個bean載入的源頭。

二、

在開始之前,先複習一下,那些以@Component註解為基礎的註解是需要配合@ComponentScan來掃描的,也就是說,我們至少需要知道掃描的範圍

那麼在springboot中,掃描的範圍是在@SpringBootApplication --> @EnableAutoConfiguration -->@AutoConfigurationPackage -->@Import(AutoConfigurationPackages.Registrar.class) 確定的

注意到,方法引數中,已經可以確定包名了

然後通過註解的元資訊,首先加入了basePackages

basePackageClasses資訊

然後如果這兩個屬性都沒有值,也就是我們使用的是預設值,那麼預設的packageNames就是這個註解標註的類所在的包,也就是我們的啟動類

那麼現在拿到了包的路徑,也就是掃描的範圍之後,就會將資訊放在類中,無論bean定義資訊中是不是已經存在,都會將這次的包資訊加入到bean中

以beanName=AutoConfigurationPackages, 具體類是BasePackages.class的形式

三、

  在知道了我們掃描的範圍之後,就可以開始找找到底是從哪裡開始,我們自定義的bean以及那些自動配置的類,是什麼時候被載入成beanDefinition的

  我們主要是找出ConfigurationClassPostProcessor這個類,是什麼時候,在哪裡載入的

這裡一個題外話,sources是什麼

其實是我們的啟動類

這個看名字就很像了,載入beanDefinition的

掃描之後讀取,在spring中也是這兩種介面完成的功能

ConfigurationClassPostProcessor註冊為beanName=internalConfigurationAnnotationProcessor

四、

  這樣,對於配置類的後置處理器的載入,就找到了,接下來就可以看看這個類的執行時機,就可以知道我們自定義的bean啥時候載入了

  首先看看這個類的繼承樹

可以看到,BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor介面都有實現,不過從名字看出來,我們想看的是BeanDefinitionRegistryPostProcessor這個介面的實現

知道介面後,根據容器的生命週期,就知道這個類的執行時機了

是在容器重新整理中的invokeBeanFactoryPostProcessors方法中被執行

在印象中,在這個方法之前的類,都是從spring.factories中直接讀出來再反射生成,並沒有通過beanFactory

在這個之後,我們就可以直接通過beanFactory拿到bean了

方法的第一段,將已經有的BeanDefinitionRegistryPostProcessor首先執行,順便一提,這些最早存在的BeanDefinitionRegistryPostProcessor是通過spring.factories檔案裡面的initializer建立的

接下來就是將還沒有例項化的BeanDefinitionRegistryPostProcessor例項化,會區分優先順序去獲取,然後排序,最後執行

之後按照這個套路對BeanFactoryPostProcessor介面再做一次

那麼可以知道我們的ConfigurationClassPostProcessor是最高優先順序,所以會最先執行postProcessBeanDefinitionRegistry方法, 然後是postProcessBeanFactory方法

知道了執行時機,接下來就可以檢視這兩個方法到底是如何為我們註冊bean的

由於方法較長,一段段看

首先是將所有的beanDefinition拿出來,然後檢視這些bean是不是有被標註了@Configuration

而且會檢查是不是有重複處理的,如果沒有被處理過,會被加入候選列表

如果沒有被標註的類,就直接返回

排序

看看是不是有自定義的beanName生成規則,

然後開始解析bean,方法很長,先不進去具體的方法看,先從整體的邏輯去分析,這一大段程式碼做了什麼

1、例項化一個 ConfigurationClassParser ,從名字猜測,就是專門解析我們的配置類的,然後是 parser.parse(candidates);

那我們知道,在一開始的時候,beanDefinition裡面除了那些約定的類,也就只有我們自己的啟動類了,而我們的啟動類也是有@Configuration的,所以一開始其實就是解析我們的啟動類

2、然後在解析啟動類之後,將解析出來的所有類減去已經解析的類

3、例項化一個 ConfigurationClassBeanDefinitionReader ,隨後 this.reader.loadBeanDefinitions(configClasses); 很明顯,就是將我們解析出來的類註冊成BeanDefinition ,然後去除已經解析的類

4、接著,清除了候選列表,判斷現階段註冊的beanDefinition是不是比一開始拿出來的要多了,多了也就是證明這一個@Configuration配合@ComponentScan或者@Bean註冊了許多bean

然後接下來就重複一開始的步驟,檢查新註冊的bean是不是還有@Configuration標註的,如果有就會繼續加入候選列表

然後這個do...while迴圈一直到候選列表中沒有可以解析的bean了,也就代表所有的@Configuration都被解析完成

5、最後註冊了一個bean暫時不知道用處,註釋看不懂,還有就是將元資訊讀取工廠的快取清除了

然後整個解析的邏輯我們就已經清楚了,接下來重點就在那些bean是如何被掃描出來的,也就是bean的元資訊和路徑資訊,有了這些才能給reader封裝為beanDefinition以供後續例項化

分為三類解析,有註解的,有抽象的,有正常的,不過都是一個方法

遞迴檢查是不是註解或者是不是@Controller等註解,然後進入真正的解析(這個遞迴檢查暫時不太理解具體作用)

又是一個較長方法,不過此處已經接近核心解析程式碼,還是先看整體

1、解析@Component

2、解析@PropertySources

帶有@ComponentScan註解的,會使用componentScanParser去解析,大概過程是將註解的元資訊basePackage、過濾規則結合,然後將讀取的類封裝,具體的執行邏輯暫時不做閱讀

1、處理@import註解

2、處理@importResource註解

3、處理帶有@Bean註解的方法

4、處理介面中的預設方法

處理父類

五、

在上面的過程中,根據不同的註解會出現許多分支,那麼在我們啟動過程中,這個被解析的類就是我們的啟動類

回去翻查啟動類的註解中,具有的元資訊是,有一個@ComponentScan定義了過濾規則,但是沒有定義basePackage

但是如果看了@ComponentScan的處理邏輯中,會發現,如果標註的@ComponentScan沒有定義basePackage,那麼會將標註的類所在包作為basePackage

(具體的程式碼可以到 this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); 檢視

這時候就會出現一個疑問,因為啟動類上還有一個@AutoConfigurationPackage,這個註解內部是@Import ,最終會註冊一個 BasePackages.class ,裡面也是帶有以當前標註類的所在包為basePackage的資訊

然後在查詢該類是何時被使用的時候,發現它的使用場景多是資料庫一側,目前沒有接觸這一方面的原始碼,但是可以看到這個註解我們暫時沒有使用

我們自定義的bean是通過@ComponentScan被掃描的

六、

  至此,對於beanDefinition的載入過程,做了一個簡述,具體到每一個註解的具體分析,就留到以後再做下一層次的原始碼閱讀。

如有錯漏,歡迎指正。