Spring 属性注入(三)AbstractNestablePropertyAccessor
Spring 属性注入(三)AbstractNestablePropertyAccessor
Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)
BeanWrapper 有两个核心的实现类
AbstractNestablePropertyAccessor
提供对嵌套属性的支持BeanWrapperImpl
提供对 JavaBean 的内省功能,如 PropertyDescriptor
AbstractNestablePropertyAccessor 是 Spring 4.2 提供的功能,将原 BeanWrapperImpl 的功能抽出,这样 BeanWrapperImpl 只提供对 JavaBean 的内省功能。
一、嵌套属性
AbstractNestablePropertyAccessor 类通过其成员属性提供了一种支持嵌套属性的数据结构,下面是几个核心的的属性成员:
属性类型及名称 | 说明 |
---|---|
Object object | 被 BeanWrapper 包装的对象 |
String nestedPath | 当前 BeanWrapper 对象所属嵌套层次的属性名,最顶层的 BeanWrapper 此属性的值为空 |
Object rootObject | 最顶层 BeanWrapper 所包装的对象 |
Map nestedBeanWrappers | 缓存当前 BeanWrapper 的嵌套属性的 nestedPath 和对应的 BeanWrapperImpl 对象 |
例如如下的类结构:
@Test
public void test() throws Exception {
BeanWrapperImpl rootBeanWrapper = new BeanWrapperImpl(Company.class);
rootBeanWrapper.setAutoGrowNestedPaths(true);
rootBeanWrapper.setPropertyValue("name", "company");
rootBeanWrapper.setPropertyValue("department.name", "company");
rootBeanWrapper.setPropertyValue("director.info.name", "info...");
rootBeanWrapper.setPropertyValue("employees[0].attrs['a']", "a");
}
public class Company {
private String name;
private int total;
private Department department;
private Employee director;
private Employee[] employees;
private Map<Department, List<Employee>> departmentEmployees;
public static class Employee {
private String name;
private double salary;
private Map<String, String> attrs;
}
public static class Department {
private String name;
}
}
rootBeanWrapper 各嵌套层的属性如下:
对象 | level | object | nestedPath | rootObject | nestedBeanWrappers |
---|---|---|---|---|---|
rootBeanWrapper | 0(根) | company | "" | company | department -> departmentBeanWrapper director -> directorBeanWrapper |
departmentBeanWrapper | 1 | department | department | company | 无 |
directorBeanWrapper | 1 | director | director | company | info -> infoBeanWrapper |
infoBeanWrapper | 2 | info | director.info | company | 无 |
二、getPropertyAccessorForPropertyPath
getPropertyAccessorForPropertyPath 根据属性(propertyPath)获取所在 bean 的包装对象 beanWrapper。如果是类似 director.info.name 的嵌套属性,则需要递归获取。真正获取指定属性的包装对象则由方法 getNestedPropertyAccessor 完成。
protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
// 1. 获取第一个点之前的属性部分。eg: director.info.name 返回 department
int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
// 2. 递归处理嵌套属性
// 2.1 先获取 director 属性所在类的 rootBeanWrapper
// 2.2 再获取 info 属性所在类的 directorBeanWrapper
// 2.3 依此类推,获取最后一个属性 name 属性所在类的 infoBeanWrapper
if (pos > -1) {
String nestedProperty = propertyPath.substring(0, pos);
String nestedPath = propertyPath.substring(pos + 1);
AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty);
return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
// 3. 当前对象直接返回
} else {
return this;
}
}
getPropertyAccessorForPropertyPath 有两种情况:
director(不包含 .)
直接返回当前 bean 的包装对象director.info.name(包含 .)
从当前对象开始递归查找,root -> director -> info。查找当前 beanWrapper 指定属性的包装对象由方法 getNestedPropertyAccessor 完成。
// 缓存已经查找过后 AbstractNestablePropertyAccessor
private Map<String, AbstractNestablePropertyAccessor> nestedPropertyAccessors;
private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) {
if (this.nestedPropertyAccessors == null) {
this.nestedPropertyAccessors = new HashMap<>();
}
// 1. 获取属性对应的 token 值,主要用于解析 attrs['key'][0] 这样 Map/Array/Collection 循环嵌套的属性
PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
String canonicalName = tokens.canonicalName;
Object value = getPropertyValue(tokens);
// 2. 属性不存在则根据 autoGrowNestedPaths 决定是否自动创建
if (value == null || (value instanceof Optional && !((Optional) value).isPresent())) {
if (isAutoGrowNestedPaths()) {
value = setDefaultValue(tokens);
} else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
}
}
// 3. 先从缓存中获取,没有就创建一个新的 nestedPa
AbstractNestablePropertyAccessor nestedPa = this.nestedPropertyAccessors.get(canonicalName);
if (nestedPa == null || nestedPa.getWrappedInstance() != ObjectUtils.unwrapOptional(value)) {
nestedPa = newNestedPropertyAccessor(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR);
// Inherit all type-specific PropertyEditors.
copyDefaultEditorsTo(nestedPa);
copyCustomEditorsTo(nestedPa, canonicalName);
this.nestedPropertyAccessors.put(canonicalName, nestedPa);
}
return nestedPa;
}
getNestedPropertyAccessor 方法使用了动态规划算法,缓存每次递归的查的结果。该方法获取当前 bean 指定属性(nestedProperty)对应的 AbstractNestablePropertyAccessor。当然这个属性可能为 attrs['key'][0] 类型,这样就需要处理 Array、Map、List、Set 这样的数据类型。PropertyTokenHolder 正是处理这种类型的属性。
三、PropertyTokenHolder
PropertyTokenHolder 用于解析嵌套属性名称,标识唯一的属性。因为类似 attrs['key'][0] 这样的属性不统一,解析后将 [] 之间的 '
和 "
去除后保存在 canonicalName 中,attrs 保存在 actualName 中,["key", "0"] 保存在 keys 中。
protected static class PropertyTokenHolder {
// 对应 bean 中的属性名称,如嵌套属性 attrs['key'][0] 在 bean 中的属性名称为 attrs
public String actualName;
// 将原始的嵌套属性处理成标准的 token,如 attrs['key'][0] 处理成 attrs[key][0]
public String canonicalName;
// 这个数组存放的是嵌套属性 [] 中的内容,如 attrs['key'][0] 处理成 ["key", "0"]
public String[] keys;
属性解析过程如下:
private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
String actualName = null;
List<String> keys = new ArrayList<>(2);
int searchIndex = 0;
while (searchIndex != -1) {
int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex);
searchIndex = -1;
if (keyStart != -1) {
int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length());
if (keyEnd != -1) {
// 1. actualName 对应 bean 中的属性名称
if (actualName == null) {
actualName = propertyName.substring(0, keyStart);
}
// 2. 删除每个 key 中间的单引和双引
String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
if (key.length() > 1 && (key.startsWith("'") && key.endsWith("'")) ||
(key.startsWith("\"") && key.endsWith("\""))) {
key = key.substring(1, key.length() - 1);
}
keys.add(key);
searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
}
}
}
PropertyTokenHolder tokens = new PropertyTokenHolder(actualName != null ? actualName : propertyName);
if (!keys.isEmpty()) {
// 3. canonicalName 为原始属性名称去除单引和双引之后的名称
tokens.canonicalName += PROPERTY_KEY_PREFIX +
StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
PROPERTY_KEY_SUFFIX;
tokens.keys = StringUtils.toStringArray(keys);
}
return tokens;
}
四、PropertyHandler
PropertyHandler 的默认实现是 BeanPropertyHandler,位于 BeanWrapperImple 内,BeanPropertyHandler 是对 PropertyDescriptor 的封装,提供了对 JavaBean 底层的操作,如属性的读写。
protected PropertyHandler getPropertyHandler(String propertyName) throws BeansException {
Assert.notNull(propertyName, "Property name must not be null");
AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName);
return nestedPa.getLocalPropertyHandler(getFinalPath(nestedPa, propertyName));
}
// PropertyHandler 是对 PropertyDescriptor 的封装,提供了对 JavaBean 底层的操作,如属性的读写
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
return (pd != null ? new BeanPropertyHandler(pd) : null);
}
五、setPropertyValue 属性注入
@Override
public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {
// 1. 递归获取 propertyName 属性所在的 beanWrapper,如 director.info.name 获取 name 属性所在的 info bean
AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName);
// 2. 获取属性的 token
PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
// 3. 设置属性值
nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
}
setPropertyValue 分以下步骤:
- 递归获取 propertyName 属性所在的 beanWrapper,如果这个属性为 null 则是否创建一个默认的值(setDefaultValue)?
- 根据属性的 tokens 进行赋值操作。这里分两种情况:一是简单的属性赋值,如 director;二是 Array、Map、List、Set 类型属性注入,如 employees[0].attrs['a']
protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
// 1. Array、Map、List、Set 类型属性注入。employees[0].attrs['a']
if (tokens.keys != null) {
processKeyedProperty(tokens, pv);
// 2. 简单 bean 属性注入。director
} else {
processLocalProperty(tokens, pv);
}
}
以 processLocalProperty 为例属性注入代码如下:
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
if (ph == null || !ph.isWritable()) {
// 处理 null 情况 ...
}
Object oldValue = null;
Object originalValue = pv.getValue();
Object valueToApply = originalValue;
if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
if (pv.isConverted()) {
valueToApply = pv.getConvertedValue();
} else {
// 1. 将 oldValue 暴露给 PropertyEditor
if (isExtractOldValueForEditor() && ph.isReadable()) {
oldValue = ph.getValue();
}
// 2. 类型转换,委托给 TypeConverterDelegate 完成
valueToApply = convertForProperty(
tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
}
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
}
// 3. 属性赋值
ph.setValue(valueToApply);
}
六、getPropertyValue
getPropertyValue 根据属性名称获取对应的值。
@Override
public Object getPropertyValue(String propertyName) throws BeansException {
AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName);
PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
return nestedPa.getPropertyValue(tokens);
}
protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
String propertyName = tokens.canonicalName;
String actualName = tokens.actualName;
PropertyHandler ph = getLocalPropertyHandler(actualName);
if (ph == null || !ph.isReadable()) {
throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
}
// 1. 反射获取属性对应的值
Object value = ph.getValue();
// 2. Array、Map、List、Set 类型需要单独处理
if (tokens.keys != null) {
if (value == null) {
if (isAutoGrowNestedPaths()) {
value = setDefaultValue(new PropertyTokenHolder(tokens.actualName));
} else {
throw new NullValueInNestedPathException();
}
}
StringBuilder indexedPropertyName = new StringBuilder(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();
} else if (value.getClass().isArray()) {
int index = Integer.parseInt(key);
value = growArrayIfNecessary(value, index, indexedPropertyName.toString());
value = Array.get(value, index);
} else if (value instanceof List) {
int index = Integer.parseInt(key);
List<Object> list = (List<Object>) value;
growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);
value = list.get(index);
} else if (value instanceof Set) {
Set<Object> set = (Set<Object>) value;
int index = Integer.parseInt(key);
if (index < 0 || index >= set.size()) {
throw new InvalidPropertyException();
}
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<Object, Object> map = (Map<Object, Object>) value;
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
value = map.get(convertedMapKey);
}
indexedPropertyName.append(PROPERTY_KEY_PREFIX).append(key).append(PROPERTY_KEY_SUFFIX);
}
}
return value;
}
七、setDefaultValue
setDefaultValue 嵌套属性为 null 时设置默认属性。
getPropertyAccessorForPropertyPath 递归获取嵌套属性,或 getPropertyValue 时,如果属性为 null 且 autoGrowNestedPaths=true 会创建默认的对象。如 director.info.name 时 director 为 null 会调用其无参构造方法创建默认对象。
private Object setDefaultValue(PropertyTokenHolder tokens) {
PropertyValue pv = createDefaultPropertyValue(tokens);
setPropertyValue(tokens, pv);
Object defaultValue = getPropertyValue(tokens);
Assert.state(defaultValue != null, "Default value must not be null");
return defaultValue;
}
private PropertyValue createDefaultPropertyValue(PropertyTokenHolder tokens) {
TypeDescriptor desc = getPropertyTypeDescriptor(tokens.canonicalName);
Object defaultValue = newValue(desc.getType(), desc, tokens.canonicalName);
return new PropertyValue(tokens.canonicalName, defaultValue);
}
// 创建一个默认的对象,type 为 Class 类型,desc 用于解析集合或 Map 的泛型类型
private Object newValue(Class<?> type, @Nullable TypeDescriptor desc, String name) {
// 1. Array 仅考虑二维数组 String[][]
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
// TODO - only handles 2-dimensional arrays
if (componentType.isArray()) {
Object array = Array.newInstance(componentType, 1);
Array.set(array, 0, Array.newInstance(componentType.getComponentType(), 0));
return array;
} else {
return Array.newInstance(componentType, 0);
}
// 2. Collection CollectionFactory.createCollection 都没有指定泛型
} else if (Collection.class.isAssignableFrom(type)) {
TypeDescriptor elementDesc = (desc != null ? desc.getElementTypeDescriptor() : null);
return CollectionFactory.createCollection(type, (elementDesc != null ? elementDesc.getType() : null), 16);
// 3. Map
} else if (Map.class.isAssignableFrom(type)) {
TypeDescriptor keyDesc = (desc != null ? desc.getMapKeyTypeDescriptor() : null);
return CollectionFactory.createMap(type, (keyDesc != null ? keyDesc.getType() : null), 16);
// 3. 简单的 Bean,直接使用默认的无参构造方法
} else {
Constructor<?> ctor = type.getDeclaredConstructor();
if (Modifier.isPrivate(ctor.getModifiers())) {
throw new IllegalAccessException("Auto-growing not allowed with private constructor: " + ctor);
}
return BeanUtils.instantiateClass(ctor);
}
}
参考:
- 《Spring 学习 (二)BeanWrapper及其实现》:https://blog.csdn.net/zhiweianran/article/details/7919129
每天用心记录一点点。内容也许不重要,但习惯很重要!
Spring 属性注入(三)AbstractNestablePropertyAccessor的更多相关文章
- Spring 属性注入(一)JavaBean 内省机制在 BeanWrapper 中的应用
Spring 属性注入(一)JavaBean 内省机制在 BeanWrapper 中的应用 Spring 系列目录(https://www.cnblogs.com/binarylei/p/101174 ...
- Spring 属性注入(二)BeanWrapper 结构
Spring 属性注入(二)BeanWrapper 结构 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) BeanWrap ...
- Spring 属性注入(四)属性键值对 - PropertyValue
Spring 属性注入(四)属性键值对 - PropertyValue Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) P ...
- spring 属性注入
Spring的核心技术室依赖注入,下面是依赖注入之属性注入的实现过程,牛刀小试,请看效果. 1.首先添加Spring.Web引用.本例中是使用分层思想来演示的,下面是项目的结构和UserModel类的 ...
- Spring依赖注入三种方式详解
在讲解Spring依赖注入之前的准备工作: 下载包含Spring的工具jar包的压缩包 解压缩下载下来的Spring压缩包文件 解压缩之后我们会看到libs文件夹下有许多jar包,而我们只需要其中的c ...
- Spring属性注入、构造方法注入、工厂注入以及注入参数(转)
Spring 是一个开源框架. Spring 为简化企业级应用开发而生(对比EJB2.0来说). 使用 Spring 可以使简单的 JavaBean 实现以前只有 EJB 才能实现的功能.Spring ...
- spring依赖注入三种方式
一.构造器注入 构造器注入是在程序中实现构造器,可以注入任意类型,如自定义类,集合,String等,注:构造器所有有final修饰的变量都必须在构造方法中注入. 二.设值注入(setter方式注入) ...
- 六 Spring属性注入的四种方式:set方法、构造方法、P名称空间、SPEL表达式
Spring的属性注入: 构造方法的属性注入 set方法的属性注入
- spring属性注入
1,set方法注入 (1)对于值类型的属性: 在对象中一定要有set方法 package com.songyan.demo1; import com.songyan.injection.Car; /* ...
随机推荐
- rectangle,boundingRect和Rect
rectangle( rook_image, Point( , *w/8.0 ), Point( w, w), Scalar( , , ), , ); 矩形将被画到图像 rook_image 上 矩形 ...
- chrome 扩展 调试
开发chrome扩展,有时候需要输出console.log 通常调试的popup.html.content_script.backgroup.html等 但是有一些是没有这些的 1.推荐一个扩展: h ...
- git ssh免登陆,以及ssh config
git去连接github或gitlab上的远程仓库,可以使用ssh方式,也可以使用git的账号密码登录 这里介绍使用ssh方式实现免登陆(第一步和第二步即可实现) 第一步:生成ssh秘钥 ssh- ...
- UML中的关联,泛化,依赖,聚集,组合(转)
转自:http://blog.sina.com.cn/s/blog_5f8b45f20100dzjo.html 关联(association): 这是一种很常见的关系,这种关系在我们的生活中到处可见, ...
- oracle 调用包体的函数并返回return值
/// <summary> /// 执行数据库包体操作,返回结果 /// </summary> /// <param name="cmdText"&g ...
- 模拟银行业务的JS实现
/*开户.存款.挂失.补卡.取款.转账.余额查询.密码修改.交易查询.锁定账号.解锁账号等*//*C#第7天 请参考by-Qy*/ using System;using System.Collecti ...
- cdh5.13.1 hadoop hdfs HA模式无法启动
经过观察日志发现,JN三个节点启动正常,只有NN节点启动时提示JN节点没有格式化 停止HDFS下面所有服务 先启动JN节点 然后启动一个NN节点,观察三个JN节点日志 发现其中一个节点的日志正常,没有 ...
- AVL树与红黑树(R-B树)的区别与联系
AVL树(http://baike.baidu.com/view/593144.htm?fr=aladdin),又称(严格)高度平衡的二叉搜索树.其他的平衡树还有:红黑树.Treap.伸展树.SBT. ...
- Python+Selenium学习--打印当前页面的title及url
场景 测试中,访问1个页面然后判断其title是否符合预期是很常见的1个用例,所谓用例不够,title来凑就是这个道理.更具体一点,假设1个页面的title应该是'hello world', 那么可以 ...
- NIO和IO(BIO)的区别及NIO编程介绍
IO(BIO)和NIO的区别:其本质就是阻塞和非阻塞的区别. 阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止. 非阻塞概念:应用程序直接可以获取已经 ...