宁用抬杠鬼,不用蜜糖嘴。
本篇文章聊聊Spring数据访问、绑定体系中一个非常重要的组成: 属性访问器(PropertyAccessor
)。
首先提醒各位,注意此接口和属性解析器(PropertyResolver
)是有本质区别的:属性解析器是用来获取配置数据的,详细使用办法可参考:【小家Spring】关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析
而属性访问器PropertyAccessor接口的作用是存/取
Bean对象的属性。为了体现这个接口它的重要性,据我目前了解我此处贴出这么一句话:
它是可以访问命名属性named properties
(例如对象的bean属性或对象中的字段)的类的公共接口。大名鼎鼎的BeanWrapper
接口也继承自它,它所在包是org.springframework.beans
(BeanWrapper
也在此包)
// @since 1.1 出现得非常早
public interface PropertyAccessor {
// 简单的说就是级联属性的分隔符。
// 比如foo.bar最终会调用getFoo().getBar()两个方法
String NESTED_PROPERTY_SEPARATOR = ".";
char NESTED_PROPERTY_SEPARATOR_CHAR = '.';
// 代表角标index的符号 如person.addresses[0] 这样就可以把值放进集合/数组/Map里了
String PROPERTY_KEY_PREFIX = "[";
char PROPERTY_KEY_PREFIX_CHAR = '[';
String PROPERTY_KEY_SUFFIX = "]";
char PROPERTY_KEY_SUFFIX_CHAR = ']';
// 此属性是否可读。若属性不存在 返回false
boolean isReadableProperty(String propertyName);
// 此出行是否可写。若属性不存在,返回false
boolean isWritableProperty(String propertyName);
// 读方法
@Nullable
Class<?> getPropertyType(String propertyName) throws BeansException;
@Nullable
TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException;
@Nullable
Object getPropertyValue(String propertyName) throws BeansException;
// 写方法
void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException;
void setPropertyValue(PropertyValue pv) throws BeansException;
// 批量设置值
void setPropertyValues(Map<?, ?> map) throws BeansException;
// 说明:PropertyValues和PropertyValue关系特别像PropertySources和PropertySource的关系
void setPropertyValues(PropertyValues pvs) throws BeansException;
// 可控制是否接受非法的字段、value值扽 ignoreUnknown/ignoreInvalid分别对应非法属性和非法value值的处理策略~
void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) throws BeansException;
void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException;
}
它的继承树如下:
最终的实现类主要有DirectFieldAccessor
和BeanWrapperImpl
,本文作为铺垫,着重聊聊DirectFieldAccessor
这个访问器实现类~
说明一下:
DirectFieldAccessFallbackBeanWrapper
它在spring-data-commons
这个jar里面,所以若你没有使用spring-data-xxx是木有此实现类的~~~
可配置的PropertyAccessor
。它是一个子接口,提供了可配置的能力,并且它还继承了PropertyEditorRegistry
、TypeConverter
等接口~~~
// @since 2.0
public interface ConfigurablePropertyAccessor extends PropertyAccessor, PropertyEditorRegistry, TypeConverter {
// 设置一个ConversionService ,用于对value值进行转换
// 它是Spring3.0后推出来替代属性编辑器PropertyEditors的方案~
void setConversionService(@Nullable ConversionService conversionService);
@Nullable
ConversionService getConversionService();
// 设置在将属性编辑器应用于属性的新值时是**否提取旧属性值**。
void setExtractOldValueForEditor(boolean extractOldValueForEditor);
boolean isExtractOldValueForEditor();
// 设置此实例是否应尝试“自动增长”包含null的嵌套路径。
// true:为null的值会自动被填充为一个默认的value值,而不是抛出异常NullValueInNestedPathException
void setAutoGrowNestedPaths(boolean autoGrowNestedPaths);
boolean isAutoGrowNestedPaths();
}
按照Spring的设计,对此接口提供了一个抽象实现:AbstractPropertyAccessor
实现了部分父类的接口以及提供一些模版实现~
// @since 2.0 它继承自TypeConverterSupport 相当于实现了TypeConverter以及PropertyEditorRegistry的所有内容
public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor {
// 这两个属性上面已经解释了~~~
private boolean extractOldValueForEditor = false;
private boolean autoGrowNestedPaths = false;
... // 省略get/set方法
// setPropertyValue是抽象方法~~~
@Override
public void setPropertyValue(PropertyValue pv) throws BeansException {
setPropertyValue(pv.getName(), pv.getValue());
}
@Override
public void setPropertyValues(Map<?, ?> map) throws BeansException {
setPropertyValues(new MutablePropertyValues(map));
}
// MutablePropertyValues和MutablePropertySources特别像,此处就不再介绍了
// 此方法把Map最终包装成了一个MutablePropertyValues,它还有个web子类:ServletRequestParameterPropertyValues
@Override
public void setPropertyValues(Map<?, ?> map) throws BeansException {
setPropertyValues(new MutablePropertyValues(map));
}
// 当然也可以直接传入一个PropertyValues 这里传入fasle,表示默认要求属性和value值必须都合法否则抛出异常
@Override
public void setPropertyValues(PropertyValues pvs) throws BeansException {
setPropertyValues(pvs, false, false);
}
@Override
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) throws BeansException {
setPropertyValues(pvs, ignoreUnknown, false);
}
// 此抽象类最重要的实现方法~~~
@Override
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException {
List<PropertyAccessException> propertyAccessExceptions = null;
// 显然绝大多数情况下,都是MutablePropertyValues~~~~ 直接拿即可
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
// 遍历一个一个执行,批量设置值最终也还是调用的单个的~~~~
// 这里面是否要抛出异常,ignoreUnknown和ignoreInvalid就生效了。分别对应NotWritablePropertyException和NullValueInNestedPathException两个异常
for (PropertyValue pv : propertyValues) {
try {
setPropertyValue(pv);
} catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
}
// Otherwise, just ignore it and continue...
} catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
}
// Otherwise, just ignore it and continue...
} catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new ArrayList<>();
}
// 把异常收集,因为是for循环,最终一次性抛出
propertyAccessExceptions.add(ex);
}
}
// If we encountered individual exceptions, throw the composite exception.
if (propertyAccessExceptions != null) {
PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[0]);
throw new PropertyBatchUpdateException(paeArray);
}
}
// 子类AbstractNestablePropertyAccessor重写了此方法
// Redefined with public visibility.
@Override
@Nullable
public Class<?> getPropertyType(String propertyPath) {
return null;
}
// 抽象方法 相当于具体的get/set方法由子类去实现的~~
@Override
@Nullable
public abstract Object getPropertyValue(String propertyName) throws BeansException;
@Override
public abstract void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException;
}
它主要完成了对PropertyEditorRegistry
和TypeConverter
等接口的间接实现,然后完成了批量操作的模版操作,但是很明显最终的落地的get/set留给子类来实现~
getPropertyValue
和setPropertyValue
是分别用于获取和设置bean的属性值的。
AbstractNestablePropertyAccessor
一个典型的实现,为其它所有使用案例提供必要的基础设施。nestable:可嵌套的,支持嵌套的
// @since 4.2
public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
@Nullable
Object wrappedObject;
private String nestedPath = "";
@Nullable
Object rootObject;
/** Map with cached nested Accessors: nested path -> Accessor instance. */
@Nullable
private Map<String, AbstractNestablePropertyAccessor> nestedPropertyAccessors;
// 默认是注册默认的属性编辑器的:defaultEditors 它几乎处理了所有的Java内置类型 包括基本类型、包装类型以及对应数组类型~~~
protected AbstractNestablePropertyAccessor() {
this(true);
}
protected AbstractNestablePropertyAccessor(boolean registerDefaultEditors) {
if (registerDefaultEditors) {
registerDefaultEditors();
}
this.typeConverterDelegate = new TypeConverterDelegate(this);
}
protected AbstractNestablePropertyAccessor(Object object) {
registerDefaultEditors();
setWrappedInstance(object);
}
protected AbstractNestablePropertyAccessor(Class<?> clazz) {
registerDefaultEditors();
// 传的Clazz 那就会反射先创建一个实例对象
setWrappedInstance(BeanUtils.instantiateClass(clazz));
}
protected AbstractNestablePropertyAccessor(Object object, String nestedPath, Object rootObject) {
registerDefaultEditors();
setWrappedInstance(object, nestedPath, rootObject);
}
// parent:不能为null
protected AbstractNestablePropertyAccessor(Object object, String nestedPath, AbstractNestablePropertyAccessor parent) {
setWrappedInstance(object, nestedPath, parent.getWrappedInstance());
setExtractOldValueForEditor(parent.isExtractOldValueForEditor());
setAutoGrowNestedPaths(parent.isAutoGrowNestedPaths());
setAutoGrowCollectionLimit(parent.getAutoGrowCollectionLimit());
setConversionService(parent.getConversionService());
}
// wrappedObject:目标对象
public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
this.wrappedObject = ObjectUtils.unwrapOptional(object);
Assert.notNull(this.wrappedObject, "Target object must not be null");
this.nestedPath = (nestedPath != null ? nestedPath : "");
// 此处根对象,若nestedPath存在的话,是可以自定义一个rootObject的~~~
this.rootObject = (!this.nestedPath.isEmpty() ? rootObject : this.wrappedObject);
this.nestedPropertyAccessors = null;
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
}
public final Object getWrappedInstance() {
Assert.state(this.wrappedObject != null, "No wrapped object");
return this.wrappedObject;
}
public final String getNestedPath() {
return this.nestedPath;
}
// 显然rootObject和NestedPath相关,默认它就是wrappedObject
public final Object getRootInstance() {
Assert.state(this.rootObject != null, "No root object");
return this.rootObject;
}
... // 简单的说,它会处理.逻辑以及[0]等逻辑 [0]对应着集合和数组都可
}
此访问器将集合和数组值转换为相应的目标集合或数组,当然还解决了级联属性(嵌套属性)的问题~
需要特别注意的是:
AbstractNestablePropertyAccessor
这个抽象类在Spring4.2后才提供~~~
它继承自AbstractNestablePropertyAccessor
,所以它肯定也就可以处理级联属性和集合数组值了。(请注意,在Spring4.2之后支持,之前是不支持的~)
// @since 2.0 出现得可比父类`AbstractNestablePropertyAccessor`要早哦~~~注意:父类的构造函数都是protected的
public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
// 缓存着每个字段的处理器FieldPropertyHandler
// ReflectionUtils.findField()根据String去找到Field对象的
private final Map<String, FieldPropertyHandler> fieldMap = new HashMap<>();
public DirectFieldAccessor(Object object) {
super(object);
}
// 这个构造器也是protected 的 所以若你想自己指定nestedPath和parent,你可以继承此类~~~
protected DirectFieldAccessor(Object object, String nestedPath, DirectFieldAccessor parent) {
super(object, nestedPath, parent);
}
...
// 实现父类的抽象方法,依旧使用DirectFieldAccessor去处理~~~
@Override
protected DirectFieldAccessor newNestedPropertyAccessor(Object object, String nestedPath) {
return new DirectFieldAccessor(object, nestedPath, this);
}
// 字段field属性处理器,使用内部类实现PropertyHandler ~~~
private class FieldPropertyHandler extends PropertyHandler {
private final Field field;
// 从此处可以看出`DirectFieldAccessor`里的field默认都是可读、可写的~~~~
public FieldPropertyHandler(Field field) {
super(field.getType(), true, true);
this.field = field;
}
...
}
}
它的功能是直接操作Bean的属性值,而代替使用get/set方法去操作Bean。它的实现原理就是简单的field.get(getWrappedInstance())
和field.set(getWrappedInstance(), value)
等。
它处理级联属性的大致步骤是:
canonicalName
canonicalName
调用其field.get()拿到此字段的值~本文以DirectFieldAccessor
为例,介绍属性访问器PropertyAccessor
的使用~
注备两个普通的JavaBean。苹果Apple:
@ToString
public class Apple {
private String color;
// 复杂类型
private Size size = new Size(); // 苹果的尺寸。 存在级联
private String[] arrStr = new String[1];
private List<String> listStr = new ArrayList<>();
private Map<Integer, String> map = new HashMap<>();
// 更为复杂的类型
private List<List<String>> listList = new ArrayList<>();
private List<Map<Integer, String>> listMap = new ArrayList<>();
public Apple() {
super();
listList.add(new ArrayList<>());
listMap.add(new HashMap<>());
}
}
尺寸Size:
@ToString
public class Size {
private Integer height;
private Integer width;
}
类Apple
属性丰富,并且统一都没有
提供get/set方法。使用DirectFieldAccessor
直接的属性访问器给其赋值:
public static void main(String[] args) {
Apple apple = new Apple();
PropertyAccessor accessor = new DirectFieldAccessor(apple);
// 设置普通属性
accessor.setPropertyValue("color", "红色");
// 设置嵌套属性(注意:此处能够正常work是因为有= new Size(),
// 否则报错:Value of nested property 'size' is null 下同~)
accessor.setPropertyValue("size.height", 10);
// 设置集合/数组属性
accessor.setPropertyValue("arrStr[0]", "arrStr");
accessor.setPropertyValue("arrStr[1]", "arrStr1"); // 注意:虽然初始化时初始化过数组了,但是仍以此处的为准
accessor.setPropertyValue("listStr[0]", "listStr");
accessor.setPropertyValue("listStr[0]", "listStr1"); // 如果角标index一样,后面覆盖前面的
// 虽然listStr是String的List,但是反射绕过了泛型 可以set进去,但一get就报错~~~需要注意这一点
//accessor.setPropertyValue("listStr[0]", new Size());
//accessor.setPropertyValue("listStr[1]", 20);
//System.out.println(apple.getListStr().get(0)); //Cannot convert value of type 'com.fsx.bean.Size' to required type 'java.lang.String'
// 设置Map:key只能是数值才行,否则是不好使的~~~~
//accessor.setPropertyValue("map['aaa']","myValue1"); //Caused by: java.lang.NumberFormatException: For input string: "aaa"
accessor.setPropertyValue("map[1]", "myValue2");
// 设置listList这种集合里的集合
accessor.setPropertyValue("listList[0][0]", "listList00");
accessor.setPropertyValue("listList[0][1]", "listList01");
//accessor.setPropertyValue("listList[1][0]","listList10"); //IndexOutOfBoundsException: Index: 1, Size: 1
//accessor.setPropertyValue("listList[1][1]","listList11"); //IndexOutOfBoundsException: Index: 1, Size: 1
// 设置listMap这种集合里面放Map
accessor.setPropertyValue("listMap[0][0]", "listMap00");
//accessor.setPropertyValue("listMap[0]['myKey']","listMapkey"); //For input string: "myKey"
// =========打印输出
System.out.println(apple); //Apple(color=红色, size=Size(height=10, width=null), arrStr=[arrStr, arrStr1], listStr=[listStr1], map={1=myValue2}, listList=[[listList00, listList01]], listMap=[{0=listMap00}])
}
从结果中是能够看出来的,使用DirectFieldAccessor
能够正确完成属性赋值。这使用DirectFieldAccessor
作为实现的话有几点使用小细节需要注意:
当然若你希望null值能够被自动初始化也是可以的,请设值:
accessor.setAutoGrowNestedPaths(true);
这样数组、集合、Map等都会为null时候给你初始化(其它Bean请保证有默认构造函数)
在实际开发中,DirectFieldAccessor
使用的场景相对较少,但有个典型应用是Spring-Data-Redis
有使用DirectFieldAccessor
来获取属性值~~~
若我们开发中只是单纯的想
直接
获取属性值,不妨可以使用它,形如这样:new DirectFieldAccessor(client).getPropertyValue("redisURI")
非常的方便~~~
当设置属性值时,少不了两样东西:
listMap[0][0]
ProperyValue
对象就是用来封装这些信息的。如果某个值要给赋值给bean属性,Spring都会把这个值包装成ProperyValue
对象。
这个类的作用是对属性访问表达式的细化和归类。比如这句代码:
.setPropertyValue("listMap[0][0]", "listMapValue00");
这句代码的含义是:为Apple的成员变量listMap的第0个元素:即为Map。然后向该Map里放入键值对:0(key)和listMapValue00(value)
。所以listMap[0][0]
一个属性访问表达式,它在PropertyTokenHolder
类里存储如下:
canonicalName:listMap[0][0]
:代表整个属性访问表达式actualName:listMap
:仅包含最外层的属性名称keys:[0, 0]
:数组的长度代表索引深度,各元素代表索引值由于每个部分各有各的作用,所以就事先分解好,包装成对象,避免重复分解。
本文介绍了PropertyAccessor
属性访问器,并且以DirectFieldAccessor
来直接操作Bean且提供了使用Demo。
通过本文的学习,能给你开辟一条新思路来操作JavaBean
,而不仅仅只是通过get/set
了,这种思维在业务开发中基本无用,但在框架设计中尤为重要~
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
[email protected] | |
微 信 | fsx641385712 |
活跃平台 |
|
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |