Spring源碼之BeanDifinition(加幾行代碼,可以產出讓隊友幾天也找不出的Bug)

前言

文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/bin392328206/six-finger
種一棵樹最好的時間是十年前,其次是現在
我知道很多人不玩qq了,但是懷舊一下,歡迎加入六脈神劍Java菜鳥學習羣,羣聊號碼:549684836 鼓勵大家在技術的路上寫博客

絮叨

同樣這篇文章也是直接copy的,因爲子路老師已經寫得很好了。我這邊相當於重新學習一遍,加深印象

如果想系統的學習spring源碼那麼第一個需要搞明白的知識便是spring當中的BeanDefinition——spring bean的建模對象;

BeanDifinition

筆者特別強調,beanDefintion的比較枯燥和晦澀難懂,但是非常非常重要,我打算寫三篇來把beanDefintion知識講完;如果你想精讀spring源碼,請你一定細讀三篇beanDefintion的知識,他是spring framework當中的基石;

那麼什麼是spring bean的建模對象呢?一言概之就是把一個bean實例化出來的模型對象?有人會問把一個bean實例化出來有Class就行了啊,Class也就是我們通常說的類對象,就是一個普通對象的建模對象,那麼爲什麼spring不能用Class來建立bean呢?很簡單,因爲Class無法完成bean的抽象,比如bean的作用域,bean的注入模型,bean是否是懶加載等等信息,Class是無法抽象出來的,故而需要一個BeanDefinition類來抽象這些信息,以便於spring能夠完美的實例化一個bean(關於什麼是bean什麼是對象,請參考我寫的第一篇循環引用筆者有解釋)

上述文字可以簡單理解spring當中的BeanDefinition就是java當中的Class Class可以用來描述一個類的屬性和方法等等其他信息 BeanDefintion可以描述springbean當中的scope、lazy,以及屬性和方法等等其他信息

用一副圖來說明java實例化一個對象的基本流程

對上圖的文字說明:假設磁盤上有N個.java文件,首先我們把這些java文件編譯成class文件,繼而java虛擬機啓動會把這些class文件load到內存,當遇到new關鍵字的時候會根據類的模板信息實例化這個對象也就是在堆上面分配內存

但是spring的bean實例化過程和一個普通java對象的實例化過程還是有區別的,同樣用一幅圖來說明一下

下面我會對這幅圖做大篇幅的說明,在springbean實例化簡圖當中我一共標記了⑤步;逐一來說明吧

前提:假設在你的項目或者磁盤上有X和Y兩個類,X是被加了spring註解的,Y沒有加spring的註解;也就是正常情況下當spring容器啓動之後通過getBean(X)能正常返回X的bean,但是如果getBean(Y)則會出異常,因爲Y不能被spring容器掃描到不能被正常實例化;

①[^1]當spring容器啓動的時候會去調用ConfigurationClassPostProcessor這個bean工廠的後置處理器完成掃描,關於什麼是bean工廠的後置處理器下文再來詳細解釋;spring完成掃描的具體源碼放到後續的文章中再來說。閱讀本文讀者只需知道掃描具體幹了什麼事情即可;其實所謂的spring掃描就是把類的信息讀取到,但是讀取到類的信息存放到哪裏呢?比如類的類型(class),比如類的名字,類的構造方法。可能有讀者會有疑問這些信息不需要存啊,直接存在class對象裏面不就可以?比如當spring掃描到X的時候Class clazzx = X.class;那麼這個classx裏面就已經具備的前面說的那些信息了,確實如此,但是spring實例化一個bean不僅僅只需要這些信息,還有我上文說到的scope,lazy,dependsOn等等信息需要存儲,所以spring設計了一個BeanDefintion的類用來存儲這些信息。

故而當spring讀取到類的信息之後②[2]會實例化一個BeanDefinition的對象,繼而調用這個對象的各種set方法存儲信息;每掃描到一個符合規則的類,spring都會實例化一個BeanDefinition對象,然後把根據類的類名生成一個bean的名字(比如一個類IndexService,spring會根據類名IndexService生成一個bean的名字indexService,spring內部有一套默認的名字生成規則,但是程序員可以提供自己的名字生成器覆蓋spring內置的,這個後面更新),

[3]繼而spring會把這個beanDefinition對象和生成的beanName放到一個map當中,key=beanName,value=beanDefinition對象;至此上圖的第①②③步完成。

這裏需要說明的是spring啓動的時候會做很多工作,不僅僅是完成掃描,在掃描之前spring還幹了其他大量事情;比如實例化beanFacctory、比如實例化類掃描器等等,這裏不討論,在以後的文章再來討論

用一段代碼和結果來證明上面的理論

Appconfig.java
@ComponentScan("com.luban.beanDefinition")
@Configuration
public class Appconfig {
}


X.java
@Component
public class X {
	public X(){
		System.out.println("X Constructor");
	}
}


Y.java
public class Y {
}


Test.java
public class Test{
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
		ac.register(Appconfig.class);
		ac.refresh();
		}
	}

上述代碼裏面有X和Y兩個類(下面有筆者運行這些代碼的結果截圖),X被註解了,Y沒註解,並且在X當中有個構造方法一旦X被實例化便會打印"X Constructor";而且在main方法的最開始打印了"start"按照上面筆者的理論spring首先會掃描X繼而把X解析稱爲一個beanDefinition對象放到map;筆者在spring的源碼org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法上打了一個斷點(invokeBeanFactoryPostProcessors方法裏面就完成了上述所說的掃描解析功能),當應用程序啓動的時候首先會打印start,繼而啓動spring容器,然後調用invokeBeanFactoryPostProcessors方法,在沒有執行該方法之前查看beanDefintionMap當中並沒有key爲"x"的元素,說明X並沒有被掃描,然後繼續執行,當執行完invokeBeanFactoryPostProcessors方法時候再次查看beanDefintionMap可以看到map當中多了一個key爲"x"的元素,其對應的value就是一個beanDefintion的對象,最後查看後臺發現還沒有打印"X Constructor"這說明這個時候X並沒有被實例化,這個例子說明spring是先把類掃描出來解析稱爲一個beanDefintion對象,然後put到beanDefintionMap後面纔會去實例化X,至於這個beanDefintionMap後面的文章我會詳細講解,本文讀者只需知道他是一個專門來存放beanDefinition的集合即可


④當spring把類所對應的beanDefintion對象存到map之後,spring會調用程序員提供的bean工廠後置處理器。什麼叫bean工廠後置處理器?在spring的代碼級別是用一個接口來表示BeanFactoryPostProcessor,只要實現這個接口便是一個bean工廠後置處理器了,BeanFactoryPostProcessor的詳細源碼解析後面文章再來分析,這裏先說一下他的基本作用。BeanFactoryPostProcessor接口在spring內部也有實現,比如第①步當中完成掃描功能的類ConfigurationClassPostProcessor便是一個spring自己實現的bean工廠後置處理器,這個類筆者認爲是閱讀spring源碼當中最重要的類,沒有之一;他完成的功能太多了,以後我們一一分析,先看一下這個類的類結構圖。

ConfigurationClassPostProcessor實現了很多接口,和本文有關的只需關注兩個接口BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor;但是由於BeanDefinitionRegistryPostProcessor是繼承了BeanFactoryPostProcessor所以讀者也可以理解這是一個接口,但是筆者更加建議你理解成兩個接口比較合適,因爲spring完成上述①②③步的功能就是調用BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法完成的,到了第④步的時候spring是執行BeanFactoryPostProcessor的postProcessBeanFactory方法;這裏可能說的有點繞,大概意思spring完成①②③的功能是調用ConfigurationClassPostProcessor的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,到了第④步spring首先會調用ConfigurationClassPostProcessor的BeanFactoryPostProcessor的postProcessBeanFactory的方法,然後在調用程序員提供的BeanFactoryPostProcessor的postProcessBeanFactory方法,所以上圖當中第④步我畫的是紅色虛線,因爲第④步可能沒有(如果程序員沒有提供自己的BeanFactoryPostProcessor;當然這裏一定得說明,即使程序員沒有提供自己擴展的BeanFactoryPostProcessor,spring也會執行內置的BeanFactoryPostProcessor也就是ConfigurationClassPostProcessor,所以上圖畫的並不標準,少了一步;即spring執行內置的BeanFactoryPostProcessor;

重點:我們用自己的話總結一下BeanFactoryPostProcessor的執行時機(不管內置的還是程序員提供)————Ⅰ:如果是直接實現BeanFactoryPostProcessor的類是在spring完成掃描類之後(所謂的掃描包括把類變成beanDefinition然後put到map之中),在實例化bean(第⑤步)之前執行;Ⅱ如果是實現BeanDefinitionRegistryPostProcessor接口的類;誠然這種也叫bean工廠後置處理器他的執行時機是在執行直接實現BeanFactoryPostProcessor的類之前,和掃描(上面①②③步)是同期執行;假設你的程序擴展一個功能,需要在這個時期做某個功能則可以實現這個接口;但是筆者至今沒有遇到這樣的需求,如果以後遇到或者看到再來補上;(說明一下當筆者發表這篇博客有一個讀者聯繫筆者說他看到了一個主流框架就是擴展了BeanDefinitionRegistryPostProcessor類,瞬間筆者如久旱遇甘霖,光棍遇寡婦;遂立馬請教;那位讀者說mybatis的最新代碼裏面便是擴展這個類來實現的,筆者記得以前mybatis是擴展沒有用到這個接口,後來筆者看了一下最新的mybatis源碼確實如那位讀者所言,在下一篇筆者分析主流框架如何擴展spring的時候更新)

這個地方讀者插一句,真的這個 Spring 的各種 PostProcess真的是強大,我在這稱他爲鉤子函數,Spring的設計強大之處的設計就是,他能做到最大限度的一個 高內聚,低耦合,並且做到面對修改關閉,面對拓展開放,正因爲這些鉤子函數,他允許我們經常去插手他的各種事情,你比如在初始化話過程中,就很多地方允許我們去幹預他的初始化,他就是做到了,如果你要干預我,就以你爲準,不然我就有一套自我運行的機制,簡直就是棒

這裏再次囉嗦一下,這一段比較枯燥和晦澀,但是非常重要,如果想做到精讀spring源碼這一段尤爲重要,建議讀者多看多理解;

那麼第④步當中提到的執行程序員提供的BeanFactoryPostProcessor到底有什麼意義呢?程序員提供BeanFactoryPostProcessor的場景在哪裏?有哪些主流框架這麼幹過呢?

首先回答第一個問題,意義在哪裏?可以看一下這個接口的方法簽名

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
throws BeansException;

其實討論這個方法的意義就是討論BeanFactoryPostProcessor的作用或者說意義,參考這個方法的一句javadoc

Modify the application context’s internal bean factory after its standard initialization 在應用程序上下文的標準初始化之後修改它的內部bean工廠

再結合這個方法的執行時機和這段javadoc我們可以理解bean工廠的後置處理器(這裏只討論直接實現BeanFactoryPostProcessor的後置處理器,不包括實現BeanDefinitionRegistryPostProcessor的後置處理器)其實是spring提供的一個擴展點(spring提供很多擴展點,學習spring源碼的一個非常重要的原因就是要學會這些擴展點,以便對spring做二次開發或者寫出優雅的插件),可以讓程序員干預bean工廠的初始化過程(重點會考);這句話最重要的幾個字是初始化過程,注意不是實例化過程 ;初始化和實例化有很大的區別的,特別是在讀spring源碼的時候一定要注意這兩個名詞;翻開spring源碼你會發現整個容器初始化過程就是spring各種後置處理器調用過程;而各種後置處理器當中大體分爲兩種;一種關於實例化的後置處理器一種是關於初始化的後置處理器,這裏不是筆者臆想出來的,如果讀者熟悉spring的後置處理器體系就可以從spring的後置處理器命名看出來spring對初始化和實例化是有非常大的區分的。

說白了就是——beanFactory怎麼new出來的(實例化)BeanFactoryPostProcessor是干預不了的,但是beanFactory new出來之後各種屬性的填充或者修改(初始化)是可以通過BeanFactoryPostProcessor來干預;可以看到BeanFactoryPostProcessor裏唯一的方法postProcessBeanFactory中唯一的參數就是一個標準的beanFactory對象——ConfigurableListableBeanFactory;既然spring在調用postProcessBeanFactory方法的時候把已經實例化好的beanFactory對象傳過來了,那麼自然而然我們可以對這個beanFactory肆意妄爲了;

雖然肆意妄爲聽起來很酷,實則很多人會很迷茫;就相當於現在送給了你一輛奧迪A6(筆者的dream car!重點會考)告訴你可以對這輛車肆意妄爲,可你如果只是會按按喇叭,那就對不起贈送者的一番美意了;其實你根本不知道可以在午夜開車這輛車去長沙的解放西路轉一圈,繼而會收貨很多意外的“愛情”;筆者舉這個例子就是想說當你拿到beanFactory對象的時候不能只會sout,那不叫肆意妄爲;我們可以幹很多事情,但是你必須要瞭解beanFactory的特性或者beanFactory的各種api,但是beanFactory這個對象太複雜了,這裏不適合展開討論,與本文相關的只要知道上述我們講到的那個beanDefintionMap(存儲beanDefintion的集合)就定義在beanFactory當中;而且他也提供額api供程序員來操作這個map,比如可以修改這個map當中的beanDefinition對象,也可以添加一個beanDefinition對象到這個map當中;看一段代碼

@Component
public class TestBeanFactoryPostPorcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		//轉換爲子類,因爲父類沒有添加beanDefintion對象的api
		DefaultListableBeanFactory defaultbf =
				(DefaultListableBeanFactory) beanFactory;


		//new一個Y的beanDefinition對象,方便測試動態添加
		GenericBeanDefinition y= new GenericBeanDefinition();
		y.setBeanClass(Y.class);
		//添加一個beanDefinition對象,原本這個Y沒有被spring掃描到
		defaultbf.registerBeanDefinition("y", y);


		//得到一個已經被掃描出來的beanDefintion對象x
		//因爲X本來就被掃描出來了,所以是直接從map中獲取
		BeanDefinition x = defaultbf.getBeanDefinition("x");
		//修改這個X的beanDefintion對象的class爲Z
		//原本這個x代表的class爲X.class;現在爲Z.class
		x.setBeanClassName("com.luban.beanDefinition.Z");
	}
	
}


項目裏面有三個類X,Y,Z其中只有X加了@Component註解;也就是當代碼執行到上面那個方法的時候只掃描到了X;beanFactory裏的beanDefinitionMap當中也只有X所對應的beanDefinition對象;筆者首先new了一個Y所對應的beanDefinition對象然後調用registerBeanDefinition("y", y);把y對應的beanDefinition對象put到beanDefinitionMap,這是演示動態添加一個自己實例化的beanDefinition對象;繼而又調用getBeanDefinition("x")得到一個已經存在的beanDefinition對象,然後調用x.setBeanClassName("Z");把x所對應的beanDefinition對象所對應的class改成了Z,這是演示動態修改一個已經掃描完成的beanDefinition對象;

測試代碼如下:public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); ac.register(Appconfig.class); ac.refresh(); //正常打印 System.out.println(ac.getBean(Y.class)); //正常打印 System.out.println(ac.getBean(Z.class)); //異常打印 //雖然X加了註解,但是被偷樑換柱了,故而異常 System.out.println(ac.getBean(X.class)); }

附圖:


總結一下上面那副圖,spring實例化一個bean其實和你提供的那個類並沒有直接關係,而是和一個beanDefintion對象所對應的那個類有直接關係(正常情況下一個beanDefinition對象會對應一個類,但是也有不正常的情況);打個庸俗的比方好比讀者你喜歡一個女生小A;而小A喜歡筆者,但是你不能說你也喜歡筆者;而且筆者還是鋼鐵直男無論如何也不被可能掰彎的;

看完這個結果你還敢輕視spring當中的建模對象beanDefintion的作用了嗎?但是BeanDefinition這個接口有太多的實現類,是一個比較複雜的體現,下一篇我慢慢把spring體現當的各種beanDefinition對象逐一介紹清楚;

**我在想如果一個只會Spring應用的程序員,哪天他得罪你了 ,你偷偷寫個配置類,讓他的Service 永遠也註冊不到Spring中去,我估計他會瘋,,哈哈當然我只是告訴大家,瞭解一下底層的代碼還是很有必要,並沒有讓大家這樣去做

那麼第④步當中提到的執行程序員提供的BeanFactoryPostProcessor到底有什麼意義呢?程序員提供BeanFactoryPostProcessor的場景在哪裏?有哪些主流框架這麼幹過呢?第一個問題我們大概回答了一下,程序員一般提供BeanFactoryPostProcessor是爲了對beanFactory做修改或者叫做干預他的初始化;能修改什麼?或者能干預什麼?這個問題比較龐大,至少你現在知道可以修改beanFactory當中的beanDefinitionMap 至於剩下兩個問題,我以後更新吧

結尾

我copy這篇文章的原因,我是真覺得講得好,對Spring源碼的理解肯定是到了一定的深度,對於我這種初涉源碼的小白都能理解,所以想着分享給大家。然後我可是跟着debug了一遍哈哈。建議所有的讀者不要看了就算了,可以看一遍,自己再debug一遍,以後有空再畫個流程圖,基本上就轉換成自己的東西了,一開始都是copy嘛。

日常求贊

好了各位,以上就是這篇文章的全部內容了,能看到這裏的人呀,都是真粉

創作不易,各位的支持和認可,就是我創作的最大動力,我們下篇文章見

六脈神劍 | 文 【原創】如果本篇博客有任何錯誤,請批評指教,不勝感激 !

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章