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

  • Post author:
  • Post category:java


属性访问器(PropertyAccessor)和我上一篇博客《Spring3.1.0实现原理分析(三).配置数据》中提到的属性解析器(PropertyResolver)从字面上看很相像,但是两个接口的作用是截然不同的。属性解析器接口是用来获取配置数据的,具体可以看上篇博客,而属性访问器接口的作用是存取Bean对象的属性,所有Spring创建的Bean对象,都使用该接口存取Bean属性值,可见该接口的重要性。

照例,上一张类结构图(我承认自己画的类图很丑)

f541d60c7e40cc228d3509c465e05aca.png

下面简单的讲解下这张类图。

一. 首先最上面三个接口分别是”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> listMap = new ArrayList>();

private Map map = new HashMap();

public Apple()

{

super();

listlistStr.add(new ArrayList());

listMap.add(new HashMap());

}

……

}

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 list, 则分别创建ArrayList,TreeSet,LinkedHashSet实例.

b.如果属性不是用接口定义的,比如像这样的声明, ArrayList list, 则直接调用newInstance方法创建实例.

c.所有集合的初始长度都是16.

3.Map  — 创建Map对象,初始长度是16.

a.如果属性是用接口(Map|SortedMap)定义的, 比如像这样的声明 Map 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> listMap = new ArrayList>(), 它的索引深度是2.

1). 第一次循环获取List指定索引元素值,值类型是Map。

2). 第二次循环获取Map指定索引(即key)的value值,返回值类型是String。

六. BeanWrapperImpl如何设置属性值,步骤如下

1.获取bean包装器,具体处理逻辑跟读属性值一样,可以参看上面。

2.根据属性名称和属性值创建PropertyValue对象 。

3.如果索引深度为零

— 如果索引深度不为零,说明访问的属性类型肯定是数组,集合,Map其中之一,并且访问的是数组,集合,Map其中的元素,反之索引深度就是零。

— 举例Apple类中定义了List listStr; 那么beanWrapper.setPropertyValue(“listStr”, new ArrayList())的索引深度是零, 而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)方法,这是一个静态方法。