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