Spring Core源码导读系列之BeanWrapper

1,320 阅读9分钟

Java是一门面向对象的编程语言,Java程序在运行过程中,其数据对象都应该是JavaBean。将外部输入转化为JavaBean对象是通过反射技术实现的,BeanWrapper就是Spring对这一块的封装。本系列文章的源码分析是基于Spring 5.0版本进行的。

** 本文原创,转载请注明出处。**

BeanWrapper包含属性读写和类型转换功能,本文重点分析属性读写相关的源码,类型转换相关的源码分析,见本系列的:Spring Core源码导读系列之TypeConverter

什么是JavaBean

JavaBean是一个Java类,该类包含:

  • 一个无参构造函数
  • 一些私有成员变量
  • 成员变量的gettersetter方法

如:

public class Company {
    private String name;
    private Employee managingDirector;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }
    public Employee getManagingDirector() { return this.managingDirector; }
    public void setManagingDirector(Employee managingDirector) { this.managingDirector = managingDirector; }
}
public class Employee {
    private String name;
    private float salary;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }
    public float getSalary() { return salary; }
    public void setSalary(float salary) { this.salary = salary; }
}

为了行文方便,这里需要明确两个定义:

  • 类的属性:外界可以通过gettersetter方法进行读写的成员变量,就是类的属性。
  • 属性路径:类与类之间存在组合关系,如Company类通过managingDirector属性组合了Employee类。当我们需要表示组合类中的属性时,就需要引入属性路径,如:managingDirector.salary。为了统一,单个属性也可以称为属性路径,如:name。有时候为了区分两者,managingDirector.salary这类型的属性路径也称为嵌套属性路径。

如何使用BeanWrapper

BeanWrapper为我们操作JavaBean对象的属性提供简单的接口:

Company company = new Company();
BeanWrapper beanWrapper = new BeanWrapperImpl(company);

// 相当于:company.setName("Some Company Inc.");
beanWrapper.setPropertyValue("name", "Some Company Inc.");

Employee employee = new Employee();
// 相当于:company.setManagingDirector(employee);
company.setPropertyValue("managingDirector", employee);

// 嵌套属性路径的使用示例,相当于:beanWrapper.getManagingDirector().setSalary(30.0F);
beanWrapper.setPropertyValue("managingDirector.salary",30.0F);

// 嵌套属性路径的使用示例,相当于:beanWrapper.getManagingDirector().getSalary();
Float salary = (Float) beanWrapper.getPropertyValue("managingDirector.salary");

可以看到,使用BeanWrapper之后,只需要通过属性路径,就可以读写Bean属性。属性路径有下面这几种写法:

|属性路径|含义| |:|:| |name|无嵌套属性路径| |account.name|嵌套的属性路径| |account[2]|ArrayCollection的元素类型属性路径| |account[COMPANYNAME]|Map的元素类型属性路径|

BeanWrapper接口概览

TypeConverter接口和PropertyEditorRegistry接口是类型转换相关的接口,在Spring Core源码导读系列之TypeConverter一文中有详细分析,这里不再赘述。

PropertyAccessor接口

public interface PropertyAccessor {
	/**
     * 获取属性值
     */
	Object getPropertyValue(String propertyName) throws BeansException;
    
    /**
     * 设置属性值,该方法有几个重载方法,这里只取一个分析
     */
    void setPropertyValue(String propertyName, Object value) throws BeansException;
    
    // 省略其他与分析无关重要方法
}

PropertyAccessor接口是操作Bean的一个核心接口,定义了读写Bean属性的方法。

ConfigurablePropertyAccessor接口

public interface ConfigurablePropertyAccessor extends PropertyAccessor, PropertyEditorRegistry, TypeConverter {
	/**
	 * 当执行setPropertyValue方法时,是否提取旧值,旧值可以通过事件监听获取到
	 */
	void setExtractOldValueForEditor(boolean extractOldValueForEditor);
	boolean isExtractOldValueForEditor();

	/**
	 * setPropertyValue过程中,如果是嵌套属性路径,如managingDirector.salary,
	 * 当managingDirector为null时,是否先为managingDirector赋默认值
	 */
	void setAutoGrowNestedPaths(boolean autoGrowNestedPaths);
	boolean isAutoGrowNestedPaths();
    
    // 省略其他与分析无关重要方法
}

ConfigurablePropertyAccessor接口是一个组合接口,同时扩展了配置项。

BeanWrapper接口

public interface BeanWrapper extends ConfigurablePropertyAccessor {
	/**
	 * 当属性类型为Collection或Array时,是否自动扩容
	 */
	void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
	int getAutoGrowCollectionLimit();
    
    /**
	 * 获取被包装对象
	 */
	Object getWrappedInstance();
    
    // 省略其他与分析无关重要方法
}

BeanWrapper的实现

BeanWrapperImpl类是BeanWrapper接口的实现,这其中与类型转换相关的PropertyEditorRegistrySupport类和TypeConverterSupport类,在Spring Core源码导读系列之TypeConverter一文中有详细分析,这里不再赘述。

AbstractPropertyAccessor类

public abstract class AbstractPropertyAccessor extends TypeConverterSupport 
												implements ConfigurablePropertyAccessor {
    // 实现ConfigurablePropertyAccessor接口定义的配置方法
	private boolean extractOldValueForEditor = false;
	private boolean autoGrowNestedPaths = false;
	// 省略extractOldValueForEditor、autoGrowNestedPaths这两个属性的getter、setter方法
    
    /**
     * 实现PropertyAccessor接口定义的所有setPropertyValue重载方法,使所有重载最终调用该签名的方法
     */
    public abstract void setPropertyValue(String propertyName, Object value) throws BeansException;
    
    // 省略其他与分析无关重要方法
}

可以看到,AbstractPropertyAccessor类完成了两个功能:

  • 实现了ConfigurablePropertyAccessor接口定义的配置方法
  • 适配了PropertyAccessor接口定义的所有setPropertyValue重载方法,仅留出一个基础方法,便于子类实现

AbstractNestablePropertyAccessor类

阅读AbstractNestablePropertyAccessor类源码之前,先来看一下其定义的两个内部类:PropertyTokenHolderPropertyHandler

  • PropertyTokenHolder类

对于携带[]的属性路径,如map[aa],解析为getMap().get("aa"),因此这里应该拆分为两个属性:mapaa。但AbstractNestablePropertyAccessor类将其组合为同一单元处理,并定义了PropertyTokenHolder内部类表示这个组合属性。

/**
 *  封装一个属性路径,目的是封装携带`[]`的属性路径,使其便于使用
 *  例:属性路径map[aa]将会表示为 actualName="map", canonicalName = "map[aa]", keys=["aa"]
 */
protected static class PropertyTokenHolder {
	public String actualName;
	public String canonicalName;
	public String[] keys;
	
	public PropertyTokenHolder(String name) {
		this.actualName = name;
		this.canonicalName = name;
	}
}

注意,PropertyTokenHolder类不会用来表示aaa.bbb这样的属性路径,AbstractNestablePropertyAccessor类会将这个路径拆分为aaabbb两个属性分别处理。也就是说,组合属性仅存在于携带[]的属性路径。

  • PropertyHandler类
/**
 * 该类封装属性读写的最基础逻辑
 * getPropertyValue方法最终调用这个类的getValue方法完成属性值读取
 * setPropertyValue方法最终调用这个类的setValue方法完成属性值设置
 */
protected abstract static class PropertyHandler {
	public abstract Object getValue() throws Exception;
	public abstract void setValue(Object value) throws Exception;
    
    // 省略其他与分析无关重要方法
}

如注释,该类封装属性读写的最基础逻辑。其中最核心的getValuesetValue交给子类实现。

最后,回到AbstractNestablePropertyAccessor类。

public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
	/**
     * 根据属性路径设置值
     */
	public void setPropertyValue(String propertyName, Object value) throws BeansException {
    	// 1、解析属性路径,生成路径上倒数第二个属性的AbstractNestablePropertyAccessor
		AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName);
		// 2、将路径上最后一个属性包装为PropertyTokenHolder类型
		PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
        // 3、设置路径上最后一个属性的值
		nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
	}
    
    /**
     * 设置路径上最后一个属性的值
     */
    protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
		if (tokens.keys != null) {
			// 设置携带[]的属性路径的值
			processKeyedProperty(tokens, pv);
		} else {
			// 设置单个属性的值
			processLocalProperty(tokens, pv);
		}
	}
    
    // 省略其他与分析无关重要方法 
}

上面分析了通过AbstractNestablePropertyAccessor类设置属性值的整体脉络,大体如下:

  1. 对于嵌套的属性路径如aa.bb.cc[k],先递归解析每一个属性,生成倒数第二个属性的AbstractNestablePropertyAccessor实例,这里就是bb
  2. 将路径上最后一个属性包装为PropertyTokenHolder类型,这里就是actualName="cc", canonicalName="cc[k]", keys=["k"]
  3. 设置路径上最后一个属性的值,这里即是bb.getCc().put("k",v)

接下来深入分析第13这两个大步骤:

  • 生成倒数第二个属性的AbstractNestablePropertyAccessor实例
/**
 * 递归生成属性路径中的AbstractNestablePropertyAccessor,返回倒数第二个属性的AbstractNestablePropertyAccessor
 * 注意,携带[]的属性路径,算作一个属性,如cc[1]算作一个属性,bb.cc[1]算作bb和cc[1]两个属性
 */
protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
	int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
	if (pos > -1) {
		// 属性路径的第一个属性
		String nestedProperty = propertyPath.substring(0, pos);
		// 属性路径截去第一个属性后的子路径
		String nestedPath = propertyPath.substring(pos + 1);
		// 创建第一个属性的AbstractNestablePropertyAccessor
		AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty);
		// 递归创建嵌套属性的AbstractNestablePropertyAccessor
		return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
	} else {
		return this;
	}
}

/**
 * 创建读写属性的AbstractNestablePropertyAccessor实例
 */
private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) {
	// 如果属性值为空,又配置了自动赋默认值,则设置默认值,否则报异常
	PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
	String canonicalName = tokens.canonicalName;
	Object value = getPropertyValue(tokens);
	if (value == null || (value instanceof Optional && !((Optional) value).isPresent())) {
		if (isAutoGrowNestedPaths()) {
			value = setDefaultValue(tokens);
		} else {
			throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
		}
	}
	
	// 新建一个AbstractNestablePropertyAccessor
	AbstractNestablePropertyAccessor nestedPa = 
		newNestedPropertyAccessor(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR);
	return nestedPa;
}

/**
 * 创建AbstractNestablePropertyAccessor实例的方法是一个抽象方法,交由子类实现
 */
protected abstract AbstractNestablePropertyAccessor newNestedPropertyAccessor(Object object, String nestedPath);

由此可见,AbstractNestablePropertyAccessor类实现了嵌套属性路径的全部逻辑。

  • 设置属性值
/**
 * 设置属性的值
 */
protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
	if (tokens.keys != null) {
		// 设置携带[]的属性的值
		processKeyedProperty(tokens, pv);
	} else {
		// 设置单个属性的值
		processLocalProperty(tokens, pv);
	}
}

可以看到,对于携带[]类型的属性和不携带[]类型的属性设置值,处理方式不一样。先分析不携带[]类型的属性设置值:

/**
 * 设置不携带[]的属性的属性值
 */
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
	// 1、获取读写值处理类
	PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);

	// 2、转换值类型
	Object valueToApply = convertForProperty(
				tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());			
	
	// 3、设置值
	ph.setValue(valueToApply);
}

/**
 * 获取读写值处理类,交由子类实现
 */
protected abstract PropertyHandler getLocalPropertyHandler(String propertyName);

可以看到,对于不携带[]类型的属性设置值,逻辑清晰明了,再来看看携带[]类型的属性设置值:

/**
 * 设置携带[]的属性的属性值
 */
private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) {
	// 1、获取actualName部分的属性值,用于接下来的[]设置值
	Object propValue = getPropertyHoldingValue(tokens);

	if (propValue.getClass().isArray()) {
		// 2、类型转换
		Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
				requiredType, ph.nested(tokens.keys.length));
				
		// 3、根据配置及需要,数组扩容
		int length = Array.getLength(propValue);
		if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
			// 扩容过程略
		}
		
		// 4、将值设置到数组中
		Array.set(propValue, arrayIndex, convertedValue);
	} else if (propValue instanceof List) {
		// 类似Array,省略
	} else if (propValue instanceof Map) {
		// 类似Array,省略
	} else {
		// 抛异常,省略
	}
}

/**
 * 获取携带[]的属性actualName部分的值,用于接下来的[]设置值
 */
private Object getPropertyHoldingValue(PropertyTokenHolder tokens) {
	// 1、创建actualName部分的PropertyTokenHolder对象
	PropertyTokenHolder getterTokens = new PropertyTokenHolder(tokens.actualName);
	
	// 2、获取actualName部分的值
	Object propValue = getPropertyValue(getterTokens);
	
	if (propValue == null) {
		// 3、当值为null且配置了自动赋默认值时,赋默认值
		if (isAutoGrowNestedPaths()) {
			propValue = setDefaultValue(getterTokens);
		}
		else {
			// 抛异常,省略
		}
	}
	return propValue;
}

对于携带[]类型的属性设置值,先获取非[]部分的属性值,再处理[]部分的属性设置值,逻辑同样简单明了。

由此可见,AbstractNestablePropertyAccessor类实现了读写[]类型属性的逻辑,子类仅仅需要实现读写简单属性的接口的方法。

BeanWrapperImpl类

public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {
	/**
	 * 读写属性值的策略类,通过反射实现属性读写
	 */
	private class BeanPropertyHandler extends PropertyHandler {		
		// 读取属性值
		public Object getValue() throws Exception {
			Method readMethod = ...
			ReflectionUtils.makeAccessible(readMethod);
			return readMethod.invoke(getWrappedInstance(), (Object[]) null);
		}

		// 设置属性值
		public void setValue(Object value) throws Exception {
			Method writeMethod = ...
			ReflectionUtils.makeAccessible(writeMethod);
			writeMethod.invoke(getWrappedInstance(), value);
		}
	}
    
    // 省略其他与分析无关重要方法 
}

如上,BeanWrapperImpl类主要实现了一个读写属性值的策略类。

总结

最后,以一张序列图总结通过BeanWrapperImpl类设置一个属性值需要的大体步骤,从图中可以清楚地看到各个相关类的职责。