屬性訪問器(PropertyAccessor)和我上一篇博客《Spring3.1.0實現原理分析(三).配置數據》中提到的屬性解析器(PropertyResolver)從字面上看很相像,但是兩個接口的作用是截然不同的。屬性解析器接口是用來獲取配置數據的,具體可以看上篇博客,而屬性訪問器接口的作用是存取Bean對象的屬性,所有Spring創建的Bean對象,都使用該接口存取Bean屬性值,可見該接口的重要性。
照例,上一張類結構圖(我承認自己畫的類圖很醜)
下面簡單的講解下這張類圖。
一. 首先最上面三個接口分別是"PropertyAccessor",“TypeConverter”,“PropertyEditorRegistry ”。PropertyAccessor是整個類圖中的核心接口;TypeConverter和PropertyEditorRegistry都是關於類型轉換接的,關於類型轉換詳細內容可以參看Spring3.1.0實現原理分析(一).類型轉換,不過很可惜我在那篇博客裏面沒提到PropertyEditorRegistry接口,這裏我簡單說下,TypeConverter是Spring類型轉換體系中最頂層的接口,然後TypeConverter接口是通過引用PropertyEditorRegistry接口實現類型轉換功能的,PropertyEditorRegistry接口則通過引用ConversionService接口最終實現類型轉換功能。總之:我們可以得出一個結論,PropertyAccessor接口的實現類必然具備類型轉換功能。
二. ConfigurablePropertyAccessor接口定義了設置ConversionService對象的方法;BeanWrapper接口中定義的方法則進一步驟明確了PropertyAccessor接口的作用是用來存取Bean對象屬性的;PropertyEditorRegistrySupport類是PropertyEditorRegistry接口的實現類,它提供了類型轉換功能;AbstractPropertyAccessor接口實現了部分超接口的方法,但是核心方法存取Bean對象屬性未實現。
三. 最下面的兩個類DirectFieldAccessor和BeanWrapperImpl實現了存取Bean對象屬性的功能,兩個類和目標對象都是一對一的關係。
1. DirectFieldAccessor : 這個實現類的功能比較弱,它只支持存取Bean中普通屬性的值,不支持嵌套屬性,也不支持索引屬性(數組|集合|Map),它的是通過調用Field.get(target)和field.set(target,value)實現功能的,在該類的構造函數中會解析傳入的目標對象,獲取其中的field對象,並緩存起來。
2. BeanWrapperImpl : 這個類就是本篇博客的核心內容了,它不僅支持存取Bean中普通屬性,而且還支持嵌套屬性,還支持索引屬性(數組|集合|Map)。它的實現原理是通過屬性描述符對象獲取讀方法和寫方法,從而存取Bean屬性值。
下面重點講解BeanWrapperImpl 具體處理邏輯。
-------------------------------------------------------華麗的分隔線-------------------------------------------------------
爲了講解BeanWrapperImpl的功能,我需要先定義一個JavaBean,在下面講解過程中會引用到,代碼如下:
public class Apple
{
private String color;
private Size size = new Size();
private String[] arrStr = new String[1];
private List<Map<Integer, String>> listMap = new ArrayList<Map<Integer,String>>();
private Map<Integer, String> map = new HashMap<Integer, String>();
public Apple()
{
super();
listlistStr.add(new ArrayList<String>());
listMap.add(new HashMap<Integer, String>());
}
......
}
public class Size
{
private Integer height;
private Integer width;
......
}
一. BeanWrapperImpl 和CachedIntrospectionResults,ExtendedBeanInfo ,GenericTypeAwarePropertyDescriptor之間的關係。
在Spring首次通過BeanWrapperImpl獲取Bean屬性描述符時,會觸發對目標Bean對象的解析,這個解析操作是由CachedIntrospectionResults的靜態方法來完成的,解析大致三個步驟:
a. 爲Bean對象創建ExtendedBeanInfo對象,該類是BeanInfo接口的實現類,這個實現類的特點是"PropertyDescriptor[] getPropertyDescriptors方法返回的屬性描述符集合僅包含具備Get或Set方法的屬性,並且其中元素根據屬性名稱升序排序";
b. 調用ExtendedBeanInfo對象的getPropertyDescriptors方法,遍歷所有的屬性描述符對象,根據屬性描述符對象創建GenericTypeAwarePropertyDescriptor對象,該類是PropertyDescriptor的實現類,好吧,其實我不是十分清楚這個實現類的特點,暫且把它當做普通的PropertyDescriptor實現類。
c. 把步驟a和步驟b獲得的對象都會被緩存起來,緩存在CachedIntrospectionResults對象中。
二. PropertyValue的作用什麼?
當設置屬性值時,少不了兩樣東西,一個是屬性訪問表達式,一個是屬性值,ProperyValue對象就是用來封裝這些信息的。如果某個值要給賦值給bean屬性,Spring會把這個值包裝成ProperyValue對象。
三. PropertyTokenHolder的作用是什麼?
這個類的作用是對屬性訪問表達式的細化和歸類,比如這樣的代碼,
beanWrapper.setPropertyValue("listMap[0][0]", "aaa"); 代碼的含義是要爲Apple的成員變量listMap的第0個元素即Map,然後要爲該Map置入鍵值對0(key)和aaa(value),listMap[0][0]就是一個屬性訪問表達式,它對應的PropertyTokenHolder對象各成員變量值如下,
- canonicalName:listMap[0][0] ---- 代表整個屬性訪問表達式
- actualName:listMap ---- 僅包含最外層的屬性名稱
- keys:[0, 0] ---- 數組的長度代表索引深度,各元素代表索引值
由於每個部分各有各的作用,所以就事先分解好,包裝成對象,避免重複分解。
四. BeanWrapperImpl的成員變量autoGrowNestedPaths的作用是什麼?
BeanWrapperImpl類的成員變量autoGrowNestedPaths的作用是控制,當Spring遇到對象屬性爲null時,是否實例化,像下面這樣代碼,
beanWrapper.setPropertyValue("listStr[0]", "abc");
代碼的含義是要爲Apple對象的屬性listStr的第0個元素賦值字符串abc,但是listStr值爲null的話如何賦值呢?此時autoGrowNestedPaths爲true的話,Spring會對listStr執行實例化, 反之爲false的話,就只能拋出異常了。各類型實例化默認值如下:
1.數組
a.如果元素類型不是數組, 創建一個長度是零的數組對象.
b.如果元素類型也是數組, 首先創建一個長度是一的數組對象,然後創建一個長度是零的數組對象,賦值給父數組的第零個元素.
2.集合 -- 創建集合對象,初始長度是16.
a.如果屬性是用接口(List|SortedSet|Set,Collection)定義的,比如像這樣的聲明 List<String> list, 則分別創建ArrayList,TreeSet,LinkedHashSet實例.
b.如果屬性不是用接口定義的,比如像這樣的聲明, ArrayList<String> list, 則直接調用newInstance方法創建實例.
c.所有集合的初始長度都是16.
3.Map -- 創建Map對象,初始長度是16.
a.如果屬性是用接口(Map|SortedMap)定義的, 比如像這樣的聲明 Map<Integer,String> map, 則分別創建LinkedHashMap,SortedMap實例.
b.如果屬性不是用接口定義的, 則直接調用newInstance方法創建實例.
4.其它類型 -- 調用類型的默認構造函數.
五. BeanWrapperImpl如何讀取屬性值,步驟如下
1.獲取bean包裝器
a.對於非嵌套屬性返回this,即執行該操作的bean包裝器.
b.對於嵌套屬性,遞歸獲取嵌套屬性所在bean的bean包裝器.
1).比如size.height, size是Apple類的一個成員變量,其類型是Size,現在要設置size對象的height屬性值,height就是嵌套屬性,最終返回的是size的bean包裝器,
2).對於size.height,Spring會先讀取Apple對象的size屬性值,如果此時size尚未實例化,並且BeanWrapperImpl類的autoGrowNestedPaths爲false的話,會拋出異常,爲true的話,會調用Size類的默認構造函數實例化後賦值個Apple對象的size屬性。
2.根據屬性名稱獲取屬性描述對象.
A. 當第一次嘗試獲取bean的屬性描述符時,會對bean的屬性執行解析操作,具體如下,
1).調用系統方法Introspector.getBeanInfo(beanClass)獲取BeanInfo對象.
2).根據步驟1返回的BeanInfo對象創建ExtendedBeanInfo對象,該類是Spring開發的BeanInfo接口實現類,這個對象的特點上面說過了。
3).調用ExtendedBeanInfo對象的getPropertyDescriptors方法,遍歷所有的屬性描述符對象,根據屬性描述符對象創建GenericTypeAwarePropertyDescriptor對象,
4). 步驟2和步驟3創建的ExtendedBeanInfo對象和GenericTypeAwarePropertyDescriptor對象集合,都會被緩存起來, 以備後續使用。
5). bean對象和ExtendedBeanInfo對象和GenericTypeAwarePropertyDescriptor對象集合是一對一的關係, 每個bean對象都對應屬於自己的ExtendedBeanInfo對象和GenericTypeAwarePropertyDescriptor對象集合。
B. 根據屬性名稱從緩存中獲取GenericTypeAwarePropertyDescriptor對象。
3.根據屬性描述符對象獲取讀方法對象.
4.調用讀方法對象獲取返回值.
5.處理返回值
a.如果返回值類型非索引類型(數組|集合|Map), 則直接返回。
b.反之遍歷索引深度,依次獲取元素值。
舉例,假設Apple類中有這麼一個屬性,List<Map<Integer, String>> listMap = new ArrayList<Map<Integer,String>>(), 它的索引深度是2.
1). 第一次循環獲取List指定索引元素值,值類型是Map。
2). 第二次循環獲取Map指定索引(即key)的value值,返回值類型是String。
六. BeanWrapperImpl如何設置屬性值,步驟如下
1.獲取bean包裝器,具體處理邏輯跟讀屬性值一樣,可以參看上面。
2.根據屬性名稱和屬性值創建PropertyValue對象 。
3.如果索引深度爲零
--- 如果索引深度不爲零,說明訪問的屬性類型肯定是數組,集合,Map其中之一,並且訪問的是數組,集合,Map其中的元素,反之索引深度就是零。
--- 舉例Apple類中定義了List<String> listStr; 那麼beanWrapper.setPropertyValue("listStr", new ArrayList<String>())的索引深度是零, 而beanWrapper.setPropertyValue("listStr[0]", "abc")的索引深度是一。
--- 對於索引深度爲零的情況, Spring如下處理:
a.根據屬性名稱獲取屬性描述符對象,當第一次嘗試獲取屬性描述符時......(這裏的處理步驟跟讀取屬性值是一樣的,可以參看上面)。
b.對值執行類型轉換(如有必要)。
c.通過屬性描符述獲取寫方法對象,
d.調用寫方法對象,對屬性賦值
4.如果索引深度不爲零
舉例: beanWrapper.setPropertyValue("listMap[0][0]", "aaa"); 處理步驟如下:
a.首先Spring會根據這樣的表達式listMap[0](即listMap[0][0]索引深度減一)獲取Apple對象中listMap屬性的第0個元素對象, 獲取到map對象。如果listMap對象尚未實例化,或listMap對象雖已實例化但是其第0個元素爲null,則根據BeanWrapperImpl類的autoGrowNestedPaths屬性值,執行實例化或拋出異常。
b.然後對key(這裏是0)和value(aaa)執行類型轉換。
c.然後把key和value添加到步驟1獲取到的map對象中。
----------------------------------------------------------華麗的分隔線----------------------------------------------------------
好了,我想能看到這裏的讀者,大概都暈了吧,我自己寫的也都快暈了!
最後總結下,Spring對Bean的屬性存取都是通過BeanWrapperImpl實現的,BeanWrapperImpl和Bean是一對一的關係,BeanWrapperImpl通過屬性的讀方法和寫方法來存取Bean屬性的。如果最終用戶希望獲取BeanWrapperImpl對象,可以使用PropertyAccessorFactory#forBeanPropertyAccess(Object)方法,這是一個靜態方法。