spring管理對象是以bean為顆粒度,在最初設計時其實是特指java beans,是以之前的注入也幾乎是清一色的set注入,直到聰明的大腦們引入了annotation後兩者才有了明顯差異,慢慢進化出spring特有的bean規範。
本篇先從設計者的初衷java beans開始,理清楚set的注入原理,然後再(如)往(果)下(有)探(時)尋(間)annotation注入。
java beans規範主要有三點:
有一個公有的無參構造器
屬性可以通過get、set、is(可以替代get,用在布爾型屬性上)方法或遵循特定命名規範的其他方法通路
可序列化
sun之是以指定beans規範,很大程度上是為ide準備的——ide可以用可視化的方式設定bean的屬性。
java bean規範通過java.beans.propertyeditor設定bean屬性,通過beaninfo描述了javabean哪些屬性是可定制以及可定制屬性與propertyeditor的對應關系(propertyname->editor)。
beaninfo與javabean之間通過兩者之間規範的命名确立,對應javabean的beaninfo采用如下的命名規範:beaninfo,如car對應的beaninfo為carbeaninfo。
jdk提供内省(introspector)暴露beaninfo,通路者通過内省間接依賴propertydescriptor進而獲得bean屬性描述,該描述包括該屬性是否開放以及使用的屬性編輯器等,最後再通過屬性編輯器編輯修改屬性。此外jdk提供一個預設屬性編輯器的管理器--propertyeditormanager,它定義常見類型的屬性編輯器(class->editor),如果某個javabean的常見類型屬性沒有通過beaninfo指定屬性編輯器,ide将自動使用propertyeditormanager中該類型的預設屬性編輯器。
對java beans的使用透着spring整個團隊的靈性,rod johnson和其團隊在對jdk元件以及第三方開源中間件的使用上,不僅知其然更知其是以然,并在此基礎上進行了合理的精簡和擴充。
舉一個例子,spring中使用了aspectj,aspectj能提供了強大的靜态織入能力,很多人也想當然的認為aspectj是aop的一種織入方式,事實上spring做了取舍,隻內建了後者的文法,保留了自身的動态織入,利用後者解析aspectj風格的表達式并生成advisor,最終對target的織入隻有jdk dynamic和cglib兩種方式。
spring beans也幾乎是對java beans的颠覆和更新,在後者的基礎上,增加了更加強大的嵌套屬性支援并有自己獨特的表達式。
bean表達式和spring表達式(spring expression)是兩碼事,前者隻是bean子產品裡内置簡易功能,後者則是一個單獨子產品,兩者既不是正交關系也不是平行關系。
bean表達式主要用于對象和集合的注入,文法很簡單,主要有兩種:
以bean.propertyname代表bean的内部屬性,并支援多級嵌套。每個點代表是一級路徑,路徑和路徑之間是嵌套和包含關系。
以propertyname[index/key]代表集合類屬性的内部元素,同樣也支援多級嵌套。
支援兩者混合使用,舉例路徑a.c.b1。這代表a的屬性c下的屬性b是個數組或者集合(假設其為aa),則這個表達式是用來對aa的第2個元素map的name key綁定值。
spring使用統一的屬性通路外觀--propertyaccessor,主要包括屬性可讀和可寫判斷以及屬性值的set和get。
propertyaccessor類似beanfactory,也是多層次結構,同樣也是使用parent屬性指向父層級。對于複合屬性,會遞歸建構相應path的propertyaccessor,依然以a.c.b1為例,其屬性通路實體結構會是:
它依賴兩個實體,分别是propertyvalue和propertytokenholder:
propertyvalue是屬性名和值的鍵值對,屬性名可以是一個簡單屬性,也可以是複合屬性即表達式。
propertytokenholder是對屬性path解析的結果,解析後有actualname、canonicalname和keys三個屬性,分别指實際名稱、規範名稱和屬性下标。在上面例子中,三者分别是:
目前層級的propertyaccessor通過actualname(實際就是對象屬性名)就可以找到相應的屬性描述,并據此展開一系列處理,例如綁定值到集合的某個元素等等。
introspector, beaninfo, propertyeditor以及propertydescriptor四架馬車在spring beans體系中依然存在, 但是後兩者扮演的角色卻大幅下降:
descriptor被封裝在propertyhandler外觀中,後者基于前者感覺property類型、名稱以及setter和getter等資訊。
editor被放入專門的registry管理去除對descriptor和預設屬性編輯器管理器的依賴。registry同時維護了預設和客戶化兩類editor緩存,客戶化緩存有兩類--類型映射(type->editor)和屬性名映射(name->editor),而預設緩存則隻有類型映射。
typeconvertdelegator,屬性值類型轉換器,依賴typedescriptor和propertyeditor,前者描述屬性類型資訊,包括屬性本身以及泛化參數(數組、集合以及map等的元素)類型資訊;後者則set以及get屬性值。
cachedintrospectionresults是個多級緩存,結構如(類型->(屬性名->屬性描述實體))。在構造時傳入類型,它通過introspector擷取beaninfo,再使用後者擷取所有propertydescriptor,然後将屬性名和描述的關系緩存到該類型層級的緩存之下。
propertyhandler持有屬性getter和bean對象,通過反射即可獲得屬性值。屬性通路實體通過handler擷取屬性目前值,并将propertyvalue注入。
值綁定過程
首先從propertyvalue擷取屬性路徑;
接着通過路徑構造propertyaccessor,基于不同路徑可能會存在多層級情況;
使用actualname擷取屬性相應的propertyhandler(propertydescriptor),并取得屬性對象;
如果屬性對象是集合類型,則循環keys,直到倒數第二層級。即如果是b1,傳回的屬性對象是b[1]這個map對象,這個map對象作為被綁定對象傳回。
用綁定值綁定該屬性對象,
如果綁定值和對象或集合元素類型不相符,則通過typeconverterdelegator進行轉換。