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
然後如果這兩個屬性都沒有值,也就是我們使用的是預設值,那麼預設的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的載入過程,做了一個簡述,具體到每一個註解的具體分析,就留到以後再做下一層次的原始碼閱讀。
如有錯漏,歡迎指正。