聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用【享学Spring】

每篇一句

宁用抬杠鬼,不用蜜糖嘴。

前言

本篇文章聊聊Spring数据访问、绑定体系中一个非常重要的组成: 属性访问器(PropertyAccessor)。
首先提醒各位,注意此接口和属性解析器(PropertyResolver)是有本质区别的:属性解析器是用来获取配置数据的,详细使用办法可参考:【小家Spring】关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析

属性访问器PropertyAccessor接口的作用是存/取Bean对象的属性。为了体现这个接口它的重要性,据我目前了解我此处贴出这么一句话:

  • 所有Spring创建的Bean对象都使用该接口存取Bean属性值

PropertyAccessor

它是可以访问命名属性named properties(例如对象的bean属性或对象中的字段)的类的公共接口。大名鼎鼎的BeanWrapper接口也继承自它,它所在包是org.springframework.beansBeanWrapper也在此包)

// @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;
}

它的继承树如下:
在这里插入图片描述
最终的实现类主要有DirectFieldAccessorBeanWrapperImpl,本文作为铺垫,着重聊聊DirectFieldAccessor这个访问器实现类~

说明一下:DirectFieldAccessFallbackBeanWrapper它在spring-data-commons这个jar里面,所以若你没有使用spring-data-xxx是木有此实现类的~~~

ConfigurablePropertyAccessor

可配置的PropertyAccessor。它是一个子接口,提供了可配置的能力,并且它还继承了PropertyEditorRegistryTypeConverter等接口~~~

// @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

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;
}

它主要完成了对PropertyEditorRegistryTypeConverter等接口的间接实现,然后完成了批量操作的模版操作,但是很明显最终的落地的get/set留给子类来实现~

getPropertyValuesetPropertyValue是分别用于获取和设置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后才提供~~~

DirectFieldAccessor

它继承自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)等。
它处理级联属性的大致步骤是:

  1. 遇上级联属性,先找出canonicalName
  2. 根据此canonicalName调用其field.get()拿到此字段的值~
  3. 若不为null(有初始值),那就继续解析此类型,循而往复即可~

PropertyAccessor使用Demo

本文以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作为实现的话有几点使用小细节需要注意:

  1. 若是级联属性、集合数组等复杂属性,初始值不能为null
  2. 使用它给属性赋值无序提供get、set方法(侧面意思是:它不会走你的get/set方法逻辑

当然若你希望null值能够被自动初始化也是可以的,请设值:accessor.setAutoGrowNestedPaths(true);这样数组、集合、Map等都会为null时候给你初始化(其它Bean请保证有默认构造函数)

在实际开发中,DirectFieldAccessor使用的场景相对较少,但有个典型应用是Spring-Data-Redis有使用DirectFieldAccessor来获取属性值~~~

若我们开发中只是单纯的想直接获取属性值,不妨可以使用它,形如这样:new DirectFieldAccessor(client).getPropertyValue("redisURI")非常的方便~~~



PropertyValue的作用什么?

当设置属性值时,少不了两样东西:

  1. 属性访问表达式:如listMap[0][0]
  2. 属性值:

ProperyValue对象就是用来封装这些信息的。如果某个值要给赋值给bean属性,Spring都会把这个值包装成ProperyValue对象。

PropertyTokenHolder的作用是什么?

这个类的作用是对属性访问表达式的细化和归类。比如这句代码:

.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了,这种思维在业务开发中基本无用,但在框架设计中尤为重要~


关注A哥

AuthorA哥(YourBatman)
个人站点www.yourbatman.cn
E-mailyourbatman@qq.com
微 信fsx641385712
活跃平台
公众号BAT的乌托邦(ID:BAT-utopia)
知识星球BAT的乌托邦
每日文章推荐每日文章推荐

BAT的乌托邦

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值