Spring3.1.0實現原理分析(四).屬性訪問器(PropertyAccessor)

     屬性訪問器(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)方法,這是一個靜態方法。

    


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