摘要: 本文首先将举例说明如何使用BeanWrapper,然后根据例子中的结果分析BeanWrapper的源码。由于在spring中BeanWrapperImpl是BeanWrapper接口的唯一实现类,所以将对BeanWrapperImpl进行源码分析。最后,将从三个方面分析BeanWrapperImpl的源码,分别是构造方法、设置属性和获取属性。

1、BeanWrapper简介

BeanWrapper接口,作为spring内部的一个核心接口,正如其名,它是bean的包裹类,即在内部中将会保存该bean的实例,提供其它一些扩展功能。同时,BeanWrapper接口还继承了PropertyAccessor, propertyEditorRegistry, TypeConverter、ConfigurablePropertyAccessor接口,所以它还提供了访问bean的属性值、属性编辑器注册、类型转换等功能。

下面我们一起回顾一下bean的实例化过程,看一下spring是怎么使用BeanWrapper。

                                                         bean的实例化过程

(1)ResourceLoader加载配置信息

(2)BeanDefinitionReader读取并解析<bean>标签,并将<bean>标签的属性转换为BeanDefinition对应的属性,并注册到BeanDefinitionRegistry注册表中。

(3)容器扫描BeanDefinitionRegistry注册表,通过反射机制获取BeanFactoryPostProcessor类型的工厂后处理器,并用这个工厂后处理器对BeanDefinition进行加工。

(4)根据处理过的BeanDefinition,实例化bean。然后BeanWrapper结合BeanDefinitionRegistry和PropertyEditorRegistry对Bean的属性赋值。

在上面的bean实例化过程中,BeanWrapper取出XML中定义的值,然后通过属性编辑器或类型转换器把xml中的字符串值转换成bean中属性对应的类型,最后以内省的方式设置到bean的属性。

其实,在BeanWrapper接口中,最核心的功能就是读取和设置bean的属性,它是通过java内省的方式完成bean属性的访问的。为了能够感性认识BeanWrapper接口,下面将通过一个例子看一下怎么使用BeanWrapper访问bean的属性。

2、BeanWrapper的使用

前面介绍过,BeanWrapper接口继承了PropertyAccessor接口,所以它具备了访问bean的属性的能力。我们看一下PropertyAccessor接口的一些方法:

public interface PropertyAccessor {

    String NESTED_PROPERTY_SEPARATOR = ".";
char NESTED_PROPERTY_SEPARATOR_CHAR = '.'; String PROPERTY_KEY_PREFIX = "[";
char PROPERTY_KEY_PREFIX_CHAR = '['; String PROPERTY_KEY_SUFFIX = "]";
char PROPERTY_KEY_SUFFIX_CHAR = ']'; boolean isReadableProperty(String propertyName); boolean isWritableProperty(String propertyName); Class<?> getPropertyType(String propertyName) throws BeansException; TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException; Object getPropertyValue(String propertyName) throws BeansException; void setPropertyValue(String propertyName, Object value) throws BeansException; void setPropertyValue(PropertyValue pv) throws BeansException; void setPropertyValues(Map<?, ?> map) throws BeansException; void setPropertyValues(PropertyValues pvs) throws BeansException; void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown)
throws BeansException; void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException; }

PropertyAccessor接口提供了访问bean属性的方法,还定义了访问嵌套属性的访问表达式的切割符,重点看一下下面两个方法:

Object getPropertyValue(String propertyName) throws BeansException;

void setPropertyValue(String propertyName, Object value) throws BeansException;

getPropertyValue和setPropertyValue是分别用于获取和设置bean的属性值的。这里的propertyName支持表达式:

表达式 说明

name

指向属性name,与getName() 或 isName() 和 setName()相对应。
account.name 指向属性account的嵌套属性name,与之对应的是getAccount().setName()和getAccount().getName()
account[2] 指向索引属性account的第三个元素,索引属性可能是一个数组(array),列表(list)或其它天然有序的容器。
account[COMPANYNAME] 指向一个Map实体account中以COMPANYNAME作为键值(key)所对应的值

举个例子。我们需要使用BeanWrapper访问它们的属性。

public class Car {
private String name;
private Wheel[] wheels; private Driver driver; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Wheel[] getWheels() {
return wheels;
} public void setWheels(Wheel[] wheels) {
this.wheels = wheels;
} public Driver getDriver() {
return driver;
} public void setDriver(Driver driver) {
this.driver = driver;
} @Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", wheels=" + Arrays.toString(wheels) +
", driver=" + driver +
'}';
}
}
public class Driver {
private int age; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return "Driver{" +
"age=" + age +
'}';
}
}
public class Wheel {
private String position; public String getPosition() {
return position;
} public void setPosition(String position) {
this.position = position;
} @Override
public String toString() {
return "Wheel{" +
"position='" + position + '\'' +
'}';
}
} public static void main(String[] args) { //左边轮子的BeanWrapper
Wheel leftWheel = new Wheel();
BeanWrapper beanWrapperOfLeftWheel = PropertyAccessorFactory.forBeanPropertyAccess(leftWheel);
PropertyValue leftPosition = new PropertyValue("position", "左边");
beanWrapperOfLeftWheel.setPropertyValue(leftPosition);
System.out.println(beanWrapperOfLeftWheel.getWrappedInstance()); //左边轮子的BeanWrapper
Wheel rightWheel = new Wheel();
BeanWrapper beanWrapperOfRightWheel = PropertyAccessorFactory.forBeanPropertyAccess(rightWheel);
PropertyValue rightPosition = new PropertyValue("position", "右边");
beanWrapperOfRightWheel.setPropertyValue(rightPosition);
System.out.println(beanWrapperOfRightWheel.getWrappedInstance()); // 驾驶员
Driver driver = new Driver();
BeanWrapper beanWrapperOfDriver = PropertyAccessorFactory.forBeanPropertyAccess(driver);
PropertyValue age = new PropertyValue("age", 20);
beanWrapperOfDriver.setPropertyValue(age);
System.out.println(beanWrapperOfDriver.getWrappedInstance()); // 车子
Car car = new Car();
BeanWrapper beanWrapperOfCar = PropertyAccessorFactory.forBeanPropertyAccess(car);
beanWrapperOfCar.setPropertyValue("name", "奔驰"); // 车名
Wheel[] wheels = {leftWheel, rightWheel}; //轮子数组
beanWrapperOfCar.setPropertyValue("wheels", wheels); //轮子
beanWrapperOfCar.setPropertyValue("driver", driver); //驾驶员
System.out.println(beanWrapperOfCar.getWrappedInstance()); // 获取驾驶员的年龄
int retrievedAge = (Integer) beanWrapperOfCar.getPropertyValue("driver.age");
System.out.println("driver age : " + retrievedAge); // 通过表达式间接设置car的wheel的width
beanWrapperOfCar.setPropertyValue("wheels[0].position", "修改过的左边");
System.out.println(beanWrapperOfCar.getWrappedInstance()); }

上面的代码已经很清楚地演示了如何使用BeanWrapper设置和获取bean的属性,有了初步的认识之后,接下来将会解析该源码。

3、解析BeanWrapper源码

BeanWrapper的实现类是BeanWrapperImpl,它包装了bean对象,缓存了bean的内省结果,并可以访问bean的属性、设置bean的属性值。除此之外,BeanWrapperImpl类提供了许多默认属性编辑器,支持多种不同类型的类型转换,例如,可以将数组、集合类型的属性转换成指定特殊类型的数组或集合。用户也可以注册自定义的属性编辑器在BeanWrapperImpl中。

另外,BeanWrapperImpl有一个cachedIntrospectionResults成员变量,它保存了被包装bean的内省分析结果。cachedIntrospectionResults有两个成员变量,一个是beanInfo,它是被包裹类的BeanInfo;另一个是propertyDescriptorCache,它缓存了被包裹类的所有属性的属性描述器PropertyDescriptor。

我们看一下BeanWrapperImpl的结构类图。

3.1 构造方法

BeanWrapperImpl重载了很多种构造方法,我们看一下通过传入bean实例作为参数的构造方法。

    public BeanWrapperImpl(Object object) {
super(object);
}

调用父类AbstractNestablePropertyAccessor的构造方法。

    protected AbstractNestablePropertyAccessor(Object object) {
registerDefaultEditors(); // 标识可以使用默认的属性编辑器
setWrappedInstance(object);
}
public void setWrappedInstance(Object object) {
setWrappedInstance(object, "", null);
}
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) { // bean设置为BeanWrapperImpl的内部变量
if (object.getClass() == javaUtilOptionalClass) {
this.wrappedObject = OptionalUnwrapper.unwrap(object);
}
else {
this.wrappedObject = object;
} this.nestedPath = (nestedPath != null ? nestedPath : "");
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.wrappedObject);
this.nestedPropertyAccessors = null; // 新建类型转换器的委托类,这里BeanWrapperImpl的实例为propertyEditorRegistry,bean为targetObject
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
}

很明显,构造方法中做了两件重要的事,一个是把bean设为内部变量,另一个是实例化了一个类型转换器的委托类,由于BeanWrapperImpl同时继承了PropertyEditorRegistrySupport,所以它作为TypeConverterDelegate的属性编辑器注册中心的帮助类存在于TypeConverterDelegate中。

3.2 设置属性

BeanWrapperImpl支持多种设置bean属性的方法。

先看一下 void setPropertyValue(String propertyName, Object value)

public void setPropertyValue(String propertyName, Object value) throws BeansException {
AbstractNestablePropertyAccessor nestedPa;
try {
//根据属性名获取BeanWrapImpl对象,支持多重属性的递归分析处理
nestedPa = getPropertyAccessorForPropertyPath(propertyName);
}
catch (NotReadablePropertyException ex) {
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
"Nested property in path '" + propertyName + "' does not exist", ex);
}
// 经过上面的递归后,获取到最终需要操作的属性的对象,下面将根据该属性对象,获取最终要操作的内嵌对象的属性,
// 生成PropertyTokenHolder,内省设置属性值
PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
}

getPropertyAccessorForPropertyPath根据属性名的表达式获取访问该属性的属性访问器AbstractNestablePropertyAccessor ,即BeanWrapperImpl 。下面的注释如果没有明白的话,请先往下看,然后再回头看这里,一切豁然开朗。

下面看一下 getPropertyAccessorForPropertyPath 是怎么处理内嵌属性的,即对象里面包含对象。

protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
// 获取第一个内嵌属性的位置,分隔符是"."
int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath); // 递归获取内嵌属性,如果propertyPath不再存在分隔符“.”,返回递归结果
if (pos > -1) { // 假设propertyPath为 wheels[0].position
String nestedProperty = propertyPath.substring(0, pos); // wheels[0].position -> wheels[0] String nestedPath = propertyPath.substring(pos + 1); // wheels[0].position -> position // 获取本轮递归中AbstractNestablePropertyAccessor的属性
AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty); return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
}
else {
return this;
}
}

该方法递归获取需要访问属性的AbstractNestablePropertyAccessor。一起看一下getNestedPropertyAccessor是怎么根据属性名获取到属性访问器的。

private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) {
if (this.nestedPropertyAccessors == null) {
this.nestedPropertyAccessors = new HashMap<String, AbstractNestablePropertyAccessor>();
} PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty); //根据属性名获取PropertyTokenHolder String canonicalName = tokens.canonicalName; Object value = getPropertyValue(tokens); // 根据PropertyTokenHolder获取该内嵌属性的实例化对象 // 该属性在bean中为null
if (value == null || (value.getClass() == javaUtilOptionalClass && OptionalUnwrapper.isEmpty(value))) {
// 如果允许自动创建属性,调用setDefaultValue创建默认的对象,否则抛异常
if (isAutoGrowNestedPaths()) {
value = setDefaultValue(tokens);
}
else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
}
} // 把上面获取到的内嵌对象的实例,包裹为一个新的BeanWrapperImpl,然后把该BeanWrapperImpl缓存到本级的缓存对象nestedPropertyAccessors。
// 例如,上一节中的Car的driver属性,在car本级,维护了一个缓存对象nestedPropertyAccessors,
// 第一次访问内嵌属性driver时,将会为driver创建PropertyAccessor,并缓存到nestedPropertyAccessors中。
AbstractNestablePropertyAccessor nestedPa = this.nestedPropertyAccessors.get(canonicalName);
if (nestedPa == null || nestedPa.getWrappedInstance() !=
(value.getClass() == javaUtilOptionalClass ? OptionalUnwrapper.unwrap(value) : value)) { // 内嵌对象的实例,包裹为一个新的BeanWrapperImpl
nestedPa = newNestedPropertyAccessor(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR); // 新的BeanWrapperImpl 继承本级的PropertyEditors.
copyDefaultEditorsTo(nestedPa);
copyCustomEditorsTo(nestedPa, canonicalName); // 缓存
this.nestedPropertyAccessors.put(canonicalName, nestedPa);
}
else { }
return nestedPa;
}

在上面代码中,getPropertyNameTokens将根据propertyName生成一个统一操作的结构PropertyTokenHolder,此类保存了属性名解析后的结构。该结构的成员如下:

属性类型及名称

说明

String canonicalName

propertyName为wheels[0]时,canonicalName为wheels[0];

propertyName为driver时,canonicalName为driver

String actualName

propertyName为driver时,actualName为driver

String[] keys

propertyName为driver时,keys为null

上面的代码 Object value = getPropertyValue(tokens) 将根据 PropertyTokenHolder 获取指定property属性的实例,一起看下这段代码。

protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
String propertyName = tokens.canonicalName;
String actualName = tokens.actualName; // 获取PropertyHandler,内部实现是从cachedIntrospectionResults中取出该属性的PropertyDescriptor,
// 然后取出属性的PropertyType, ReadMethod , WriteMethod
PropertyHandler ph = getLocalPropertyHandler(actualName); // 属性不可读,抛异常
if (ph == null || !ph.isReadable()) {
throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
} try {
// 内省方式获取属性的实例
Object value = ph.getValue(); // 如果该属性是数组、list、set,tokens的keys是不为空的,keys将会保存需要访问的索引号,
// 在map中,keys是一个字符串
// 下面就是通过该索引号获取特定下标的属性值。
if (tokens.keys != null) {
if (value == null) {
if (isAutoGrowNestedPaths()) {
value = setDefaultValue(tokens.actualName);
}
else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
"Cannot access indexed value of property referenced in indexed " +
"property path '" + propertyName + "': returned null");
}
} String indexedPropertyName = tokens.actualName; // apply indexes and map keys
for (int i = 0; i < tokens.keys.length; i++) {
String key = tokens.keys[i];
if (value == null) {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
"Cannot access indexed value of property referenced in indexed " +
"property path '" + propertyName + "': returned null");
}
else if (value.getClass().isArray()) { //处理数组
int index = Integer.parseInt(key);
value = growArrayIfNecessary(value, index, indexedPropertyName);
value = Array.get(value, index);
}
else if (value instanceof List) { //处理list
int index = Integer.parseInt(key);
List<Object> list = (List<Object>) value;
growCollectionIfNecessary(list, index, indexedPropertyName, ph, i + 1);
value = list.get(index);
}
else if (value instanceof Set) { //处理set
// Apply index to Iterator in case of a Set.
Set<Object> set = (Set<Object>) value;
int index = Integer.parseInt(key); Iterator<Object> it = set.iterator();
for (int j = 0; it.hasNext(); j++) {
Object elem = it.next();
if (j == index) {
value = elem;
break;
}
}
}
else if (value instanceof Map) { //处理map
Map<Object, Object> map = (Map<Object, Object>) value;
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
// IMPORTANT: Do not pass full property name in here - property editors
// must not kick in for map keys but rather only for map values.
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
value = map.get(convertedMapKey);
}
else { }
indexedPropertyName += PROPERTY_KEY_PREFIX + key + PROPERTY_KEY_SUFFIX;
}
}
return value;
} //异常,忽略
}

总结:在通过setPropertyValue设置bean的属性时,首先将会根据propertyName的字符串,递归获取该属性所在的内嵌属性(假如属性不在内嵌属性,获取的就是它自己),然后通过内省的方式设置该属性的值。

3.3 获取属性

BeanWrapperImpl有两个获取属性的方法

public Object getPropertyValue(String propertyName) throws BeansException

protected Object getPropertyValue(PropertyTokenHolder tokens)

其中 getPropertyValue(PropertyTokenHolder tokens) 在上面设置属性那一节已经出现过了,它是在内部使用的,不对外公开。

既然如此,看一下Object getPropertyValue(String propertyName)这个方法吧。该方法在AbstractNestablePropertyAccessor中实现。

    public Object getPropertyValue(String propertyName) throws BeansException {
AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName);
PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
return nestedPa.getPropertyValue(tokens);
}

其中,getPropertyAccessorForPropertyPath方法在上一节中已经有详细的解析,它将递归获取可以访问该属性的AbstractNestablePropertyAccessor,这里的实现类是BeanWrapperImpl。例如propertyName是driver.age,那么BeanWrapperImpl所包裹的就是Driver的实例。然后,根据PropertyTokenHolder获取属性值。这个流程与设置属性的相似,请参考前面。

转自:https://my.oschina.net/thinwonton/blog/1492224

Spring源码阅读(八)的更多相关文章

  1. Bean实例化(Spring源码阅读)-我们到底能走多远系列(33)

    我们到底能走多远系列(33) 扯淡: 各位:    命运就算颠沛流离   命运就算曲折离奇   命运就算恐吓着你做人没趣味   别流泪 心酸 更不应舍弃   ... 主题: Spring源码阅读还在继 ...

  2. 初始化IoC容器(Spring源码阅读)

    初始化IoC容器(Spring源码阅读) 我们到底能走多远系列(31) 扯淡: 有个问题一直想问:各位你们的工资剩下来会怎么处理?已婚的,我知道工资永远都是不够的.未婚的你们,你们是怎么分配工资的? ...

  3. Spring源码阅读-ApplicationContext体系结构分析

    目录 继承层次图概览 ConfigurableApplicationContext分析 AbstractApplicationContext GenericApplicationContext Gen ...

  4. Sping学习笔记(一)----Spring源码阅读环境的搭建

    idea搭建spring源码阅读环境 安装gradle Github下载Spring源码 新建学习spring源码的项目 idea搭建spring源码阅读环境 安装gradle 在官网中下载gradl ...

  5. Spring源码阅读笔记02:IOC基本概念

    上篇文章中我们介绍了准备Spring源码阅读环境的两种姿势,接下来,我们就要开始探寻这个著名框架背后的原理.Spring提供的最基本最底层的功能是bean容器,这其实是对IoC思想的应用,在学习Spr ...

  6. Spring源码阅读 之 配置的读取,解析

    在上文中我们已经知道了Spring如何从我们给定的位置加载到配置文件,并将文件包装成一个Resource对象.这篇文章我们将要探讨的就是,如何从这个Resouce对象中加载到我们的容器?加载到容器后又 ...

  7. 搭建 Spring 源码阅读环境

    前言 有一个Spring源码阅读环境是学习Spring的基础.笔者借鉴了网上很多搭建环境的方法,也尝试了很多,接下来总结两种个人认为比较简便实用的方法.读者可根据自己的需要自行选择. 方法一:搭建基础 ...

  8. Spring源码阅读系列总结

    最近一段时间,粗略的查看了一下Spring源码,对Spring的两大核心和Spring的组件有了更深入的了解.同时在学习Spring源码时,得了解一些设计模式,不然阅读源码还是有一定难度的,所以一些重 ...

  9. Spring源码阅读笔记

    前言 作为一个Java开发者,工作了几年后,越发觉力有点不从心了,技术的世界实在是太过于辽阔了,接触的东西越多,越感到前所未有的恐慌. 每天捣鼓这个捣鼓那个,结果回过头来,才发现这个也不通,那个也不精 ...

  10. idea构建spring源码阅读环境

    注:由于文章不是一次性完成,下文中的test1目录和test目录应为同一个目录. (一)安装git和Gradle Spring项目托管在github之上,基于Gradle来构建项目.所以要想搭建Spr ...

随机推荐

  1. sharepoint webapp 部署注意点

    只有在配置文件或 Page 指令中将 enableSessionState 设置为 true 时,才能使用会话状态.还请确保在应用程序配置的 // 节中包括 System.Web.SessionSta ...

  2. vue.cli项目中src目录每个文件夹和文件的用法

    assets文件夹是放静态资源:components是放组件:router是定义路由相关的配置:view视图:app.vue是一个应用主组件:main.js是入口文件:

  3. oracle中nvarchar2()和varchar2()的区别

    1.NVARCHAR2(10)是可以存进去10个汉字的,如果用来存英文也只能存10个字符. 2.而VARCHAR2(10)的话,则只能存进5个汉字,英文则可以存10个.

  4. 10.6-uC/OS-III内部任务(统计任务 OS_StatTask())

    1.这个任务能够统计总的CPU使用率(0到100%),每个任务的CPU使用率( 0到100%),每个任务的堆栈使用量. 2.统计任务在uC/OS-III中是可选的,当设置OS_CFG.H中的OS_CF ...

  5. 剑指offer-矩阵覆盖

    题目描述 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形.请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?   使用dp,当n时,选着竖着放一个,那么后面的可能性为f( ...

  6. 自定义UIProgressView

    自定义CustomporgressView #import <UIKit/UIKit.h> @interface CustomporgressView : UIView @property ...

  7. Python3学习之路~2.9 字符编码与转码

    详细文章: http://www.cnblogs.com/yuanchenqi/articles/5956943.html http://www.diveintopython3.net/strings ...

  8. pycharm的小问题之光标

    一大早起来,突然发现pycharm的光变粗,按退格键会删除编写的内容,超级难受(如下图), 百度一下,也不知道在百度框里输什么关键字好,但最后还是找到了,哈哈.... ​ 解决方法: 1.按键盘上In ...

  9. os.path.join路径拼接

    #import os print("0:", os.path.join('/aaa', 'bbb', 'ccc.txt')) #0: /aaa\bbb\ccc.txt 多数这种用法 ...

  10. vue-devtools : vue的调试工具及log显示工具 vconsole

    使用 devtools 有很多好处,比如它可以让你能够实时编辑数据属性并立即看到其反映出来的变化.另一个主要的好处是能够为 Vuex 提供时间旅行式的调试体验. 注意:请留意如果页面使用了一个生产环境 ...