Spring3.1.0实现原理分析(四).属性访问器(PropertyAccessor)

来源:互联网 发布:无线路由器品牌 知乎 编辑:程序博客网 时间:2024/06/07 11:39

     属性访问器(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)方法,这是一个静态方法。

    


0 0
原创粉丝点击