Spring IoC之BeanWrapper

hresh 372 0

Spring IoC之BeanWrapper

概述

从上节 Spring IoC之ApplicationContext 解读完毕之后,关于 Spring IoC 中比较重要的5个组件我们已经分析完毕,但是这只是 IoC 容器涉及到的比较重要的组件,还有很多其他组件起着重要的作用。所以接下来的文章我们会对其他一些类做必要的解读。

org.springframework.beans.BeanWrapper 是 Spring 框架中重要的组件类。在 Spring IoC之AbstractBeanFactory(二) 一章中,我们介绍了各种 Scope 的 bean 的创建过程,在 doCreateBean()方法中在对 bean 实例填充属性前会调用 createBeanInstance(beanName, mbd, args) 方法简单初始化一个实例,该实例的类型还不是我们最后想要的 bean 类型,而是 BeanWrapper 实例。本章就是对这个类型进行简单的介绍。

BeanWrapper 简介

BeanWrapper 是 Spring 的低级 JavaBeans 基础结构的中央接口, 相当于一个代理器, 提供用于分析和操作标准 JavaBean 的操作:获得和设置属性值(单独或批量),获取属性描述符以及查询属性的可读性/可写性的能力。BeanWrapper 大部分情况下是在 Spring IoC 内部进行使用,通过 BeanWrapper,Spring IoC 容器可以用统一的方式来访问 bean 的属性。用户很少需要直接使用 BeanWrapper 进行编程。

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

bean 的实例过程:

  • ResourceLoader 加载配置信息生成 Resource;
  • BeanDefinitionReader 读取并解析标签,并将标签的属性转换为 BeanDefinition 对应的属性,并注册到 BeanDefinitionRegistry 表中;
  • 容器扫描 BeanDefinitionRegistry 注册表,通过反射机制(具体实现是在refresh方法中的invokeBeanFactoryPostProcessors方法)获取 BeanFactoryPostProcessor 类型的工厂后处理器,并用该处理器对 BeanDefinition 进行加工;
  • 根据处理过的 BeanDefinition,实例化 bean。然后 BeanWrapper 结合 BeanDefinitionRegistry 和 PropertyEditorRegistry 对 bean 的属性赋值。

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

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

BeanWrapper 使用场景

在创建 bean 的过程中,也就是 AbstractAutowireCapableBeanFactory 类中的 doCreateBean()方法中,框架利用 BeanWrapper 来封装实例化的 bean。首先执行 createBeanInstance()创建一个 BeanWrapper 对象,在该方法中有一个 autowireConstructor()方法,该方法的具体实现在 ConstructorResolver 类中:

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
    BeanWrapperImpl bw = new BeanWrapperImpl();
    this.beanFactory.initBeanWrapper(bw);
    Constructor<?> constructorToUse = null;
    ConstructorResolver.ArgumentsHolder argsHolderToUse = null;
    Object[] argsToUse = null;

    ......

    Assert.state(argsToUse != null, "Unresolved constructor arguments");
    bw.setBeanInstance(this.instantiate(beanName, mbd, constructorToUse, argsToUse));
    return bw;
}

该方法即为创建 BeanWrapper 对象的过程,这里不做过多分析,有兴趣的朋友可以自行学习。

BeanWrapper 的使用

新建两个 bean 类:Man 类和 Car 类。

Car.java

public class Car {
    private int maxSpeed ;
    private String brand ;
    private double price ;

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "maxSpeed=" + maxSpeed +
                ", brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}

Man.java

public class Man {
    private String name;
    private int age;
    private Car car;
    private String[] hobbies;
    private Map<String,Object> relatives;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public String[] getHobbies() {
        return hobbies;
    }

    public void setHobbies(String[] hobbies) {
        this.hobbies = hobbies;
    }

    public Map<String, Object> getRelatives() {
        return relatives;
    }

    public void setRelatives(Map<String, Object> relatives) {
        this.relatives = relatives;
    }

    @Override
    public String toString() {
        return "Man{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", car=" + car +
                '}';
    }
}

测试代码

@Test
public void doBeanWrapper(){
    Car car = new Car();
    BeanWrapper beanWrapperOfCar = PropertyAccessorFactory.forBeanPropertyAccess(car);
    PropertyValue brandValue = new PropertyValue("brand","东风");
    PropertyValue maxSpeedValue = new PropertyValue("maxSpeed",333);
    PropertyValue priceValue = new PropertyValue("price",202020);

    beanWrapperOfCar.setPropertyValue(brandValue);
    beanWrapperOfCar.setPropertyValue(maxSpeedValue);
    beanWrapperOfCar.setPropertyValue(priceValue);

    Man person = new Man();
    BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(person);

    PropertyValue nameValue = new PropertyValue("name","hresh");
    PropertyValue ageValue = new PropertyValue("age",23);
    PropertyValue carValue = new PropertyValue("car",car);
    String[] hobbies = {"跑步","唱歌","看书"};
    PropertyValue hobbyValue = new PropertyValue("hobbies",hobbies);
    Map<String,Object> relatives = new HashMap<>();
    relatives.put("Father","hresh");
    relatives.put("Son","hresh");
    PropertyValue relativeValue = new PropertyValue("relatives",relatives);

    beanWrapper.setPropertyValue(nameValue);
    beanWrapper.setPropertyValue(ageValue);
    beanWrapper.setPropertyValue(carValue);
    beanWrapper.setPropertyValue(hobbyValue);
    beanWrapper.setPropertyValue(relativeValue);

    System.out.println(person);
    System.out.println(beanWrapper.getWrappedInstance());
    System.out.println(person == beanWrapper.getWrappedInstance());

    int age = (Integer) beanWrapper.getPropertyValue("age");
    String hobby = (String) beanWrapper.getPropertyValue("hobbies[1]");
    String brand = (String) beanWrapper.getPropertyValue("car.brand");
    String relative = (String) beanWrapper.getPropertyValue("relatives['Father']");

    String result = String.format("%s is %d years old ,is interested in %s, has a relative named %s , also has a %s car !",person.getName(),age,hobby,relative,brand);
    System.out.println(result);
}

执行结果为:

Man{name='hresh', age=23, car=Car{maxSpeed=333, brand='东风', price=202020.0}}
Man{name='hresh', age=23, car=Car{maxSpeed=333, brand='东风', price=202020.0}}
true
hresh is 23 years old ,is interested in 唱歌, has a relative named hresh , also has a 东风 car !

以上结果即为 BeanWrapper 设置和获取 bean 属性的实现,有了初步的认识之后,接着我们分析一下该源码。

BeanWrapper 源码解析

BeanWrapper 源码定义如下:

public interface BeanWrapper extends ConfigurablePropertyAccessor {
    //指定数组和集合自动增长的限制。
    void setAutoGrowCollectionLimit(int var1);

    //返回数组和集合自动增长的限制。
    int getAutoGrowCollectionLimit();

    //返回此对象包装的bean实例。
    Object getWrappedInstance();

    //返回包装的bean实例的类型。
    Class<?> getWrappedClass();

    //获取包装对象的PropertyDescriptors(由标准JavaBean自省确定)。
    PropertyDescriptor[] getPropertyDescriptors();

    //获取包装对象的特定属性的属性描述符。
    PropertyDescriptor getPropertyDescriptor(String var1) throws InvalidPropertyException;
}

BeanWrapper 接口本身的定义并没有很多内容,但是继承了 ConfigurablePropertyAccessor 接口,该接口又继承了 PropertyAccessor, PropertyEditorRegistry, TypeConverter 这三个接口。它们的结构关系图如下:

Spring IoC之BeanWrapper

属性设置和获取定义

在 PropertyAccessor 接口中定义了方法,使得 BeanWrapper 具备了访问 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 var1);

    //确定指定的属性是否可写。
    boolean isWritableProperty(String var1);

    //确定指定属性的属性类型,或者检查属性描述符,或者在使用索引或映射元素的情况下检查值。
    @Nullable
    Class<?> getPropertyType(String var1) throws BeansException;

    //返回指定属性的类型描述符:最好从read方法返回到write方法。
    @Nullable
    TypeDescriptor getPropertyTypeDescriptor(String var1) throws BeansException;

    //获取指定属性的当前值。
    @Nullable
    Object getPropertyValue(String var1) throws BeansException;

    //将指定值设置为当前属性值。
    void setPropertyValue(String var1, @Nullable Object var2) throws BeansException;

    //将指定值设置为当前属性值。
    void setPropertyValue(PropertyValue var1) throws BeansException;

    //从map执行批量更新。
    void setPropertyValues(Map<?, ?> var1) throws BeansException;

    //执行批量更新的首选方法。
    void setPropertyValues(PropertyValues var1) throws BeansException;

    //执行批处理更新,以更好地控制行为。
    void setPropertyValues(PropertyValues var1, boolean var2) throws BeansException;

    //执行对行为的完全控制的批处理更新。
    void setPropertyValues(PropertyValues var1, boolean var2, boolean var3) 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()和setName()相对应
car.brand 指向属性car的嵌套属性brand,与之对应的是getCar().getBrand()和getCar().setBrand()
hobbies[1] 指向索引属性hobbies的第二个元素,索引属性可能是一个数组、列表或其他天然有序的容器
relatives['Father'] 指向一个Map实体relatives中以“Father”作为键值(key)所对应的值

属性设置和获取具体实现

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

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

Spring IoC之BeanWrapper

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

Spring IoC之BeanWrapper

接着针对 BeanWrapperImpl 中的重要方法进行解读。

构造方法

在测试案例中有这么一行代码:

BeanWrapper beanWrapperOfCar = PropertyAccessorFactory.forBeanPropertyAccess(car);

实际执行的是 new BeanWrapperImpl(target),这只是 BeanWrapperImpl 中的一种构造方法,该类重载了很多种构造方法,我们看一下 bean 实例作为参数的构造方法。

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

调用父类 AbstractNestablePropertyAccessor 的构造方法。

protected AbstractNestablePropertyAccessor(Object object) {
    this.autoGrowCollectionLimit = 2147483647;
    this.nestedPath = "";
    // 标识可以使用默认的属性编辑器
    this.registerDefaultEditors();
    this.setWrappedInstance(object);
}
public void setWrappedInstance(Object object) {
    this.setWrappedInstance(object, "", (Object)null);
}

public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
    // bean设置为BeanWrapperImpl的内部变量
    this.wrappedObject = ObjectUtils.unwrapOptional(object);
    Assert.notNull(this.wrappedObject, "Target object must not be null");
    this.nestedPath = nestedPath != null ? nestedPath : "";
    this.rootObject = !this.nestedPath.isEmpty() ? rootObject : this.wrappedObject;
    this.nestedPropertyAccessors = null;
    // 新建类型转换器的委托类,这里BeanWrapperImpl的实例为propertyEditorRegistry,bean为targetObject
    this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
}

从上述代码可以看出构造方法做了两件重要的事情,一是把传进来的 bean 设为内部变量,二是实例化了一个类型转换器的委托类。由于 BeanWrapperImpl 同时继承了 PropertyEditorRegistrySupport,所以它作为 TypeConverterDelegate 的属性编辑器注册中心的帮助类存在于 TypeConverterDelegate 中。

设置属性

BeanWrapperImpl 支持多种设置 bean 属性的方法,不过都是继承父类 AbstractNestablePropertyAccessor 中的。

先看一下 setPropertyValue(String propertyName, @Nullable Object value)方法。

public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {
    AbstractNestablePropertyAccessor nestedPa;
    try {
        //根据属性名获取BeanWrapImpl对象,支持多重属性的递归分析处理
        nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);
    } catch (NotReadablePropertyException var5) {
        throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var5);
    }

     // 经过上面的递归后,获取到最终需要操作的属性的对象,下面将根据该属性对象,获取最终要操作的内嵌对象的属性,
    // 生成PropertyTokenHolder,内省设置属性值
    AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));
    nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
}

getPropertyAccessorForPropertyPath()方法根据属性名的表达式获取访问该属性的属性访问器 AbstractNestablePropertyAccessor,即 BeanWrapperImpl。这里解释一下属性名表达式是什么意思,还是用例子来说明吧。

@Test
public void doBeanWrapper(){
    Car car = new Car();

    Man person = new Man();
    BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(person);

    PropertyValue nameValue = new PropertyValue("name","hresh");
    PropertyValue ageValue = new PropertyValue("age",23);
    PropertyValue carValue = new PropertyValue("car",car);
    String[] hobbies = {"跑步","唱歌","看书"};
    PropertyValue hobbyValue = new PropertyValue("hobbies",hobbies);
    Map<String,Object> relatives = new HashMap<>();
    relatives.put("Father","hresh");
    relatives.put("Son","hresh");
    PropertyValue relativeValue = new PropertyValue("relatives",relatives);

    // beanWrapper.setPropertyValue(nameValue);
    beanWrapper.setPropertyValue("name","hresh");
    beanWrapper.setPropertyValue(ageValue);
    beanWrapper.setPropertyValue(carValue);
    beanWrapper.setPropertyValue("car.brand","东方红");
    beanWrapper.setPropertyValue(hobbyValue);
    beanWrapper.setPropertyValue("hobbies[1]","跳舞");
    beanWrapper.setPropertyValue(relativeValue);
    beanWrapper.setPropertyValue("relatives['Father']","clearLove");

    System.out.println(person);

}

propertyName 即属性名,它可能只是普通的字符串,类似于"name",也有可能是"car.brand"这样的表达式,当存在这种内嵌属性的情况,我们看一下它是怎么处理的。具体看 getPropertyAccessorForPropertyPath()方法。

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

结合上述的测试案例,我们看一下调试过程中的结果展示,当执行 beanWrapper.setPropertyValue("name","hresh")语句时,

Spring IoC之BeanWrapper

此时返回的属性访问器(即 BeanWrapperImpl )中包含的 bean 属性值为 Man 类。

当执行 beanWrapper.setPropertyValue("car.brand","东方红");语句时,

Spring IoC之BeanWrapper

发现存在内嵌属性,则会进行分割,然后按序处理,先处理第一个属性,即”car“属性。此时调用 getNestedPropertyAccessor()方法,一起看下该方法的定义:

private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) {
    if (this.nestedPropertyAccessors == null) {
        this.nestedPropertyAccessors = new HashMap();
    }

    //根据属性名获取PropertyTokenHolder
    AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = this.getPropertyNameTokens(nestedProperty);
    //值为 car
    String canonicalName = tokens.canonicalName;
    // 根据PropertyTokenHolder获取该内嵌属性的实例化对象,在本例中即为car对象
    Object value = this.getPropertyValue(tokens);
    //由于我们已经先setPropertyValue(carValue),所以这里取到的value
    if (value == null || value instanceof Optional && !((Optional)value).isPresent()) {
        // 如果允许自动创建属性,调用setDefaultValue创建默认的对象,否则抛异常
        if (!this.isAutoGrowNestedPaths()) {
            throw new NullValueInNestedPathException(this.getRootClass(), this.nestedPath + canonicalName);
        }

        value = this.setDefaultValue(tokens);
    }

    // 把上面获取到的内嵌对象的实例,包裹为一个新的BeanWrapperImpl,然后把该BeanWrapperImpl缓存到本级的缓存对象nestedPropertyAccessors。
    AbstractNestablePropertyAccessor nestedPa = (AbstractNestablePropertyAccessor)this.nestedPropertyAccessors.get(canonicalName);
    if (nestedPa != null && nestedPa.getWrappedInstance() == ObjectUtils.unwrapOptional(value)) {
        if (logger.isTraceEnabled()) {
            logger.trace("Using cached nested property accessor for property '" + canonicalName + "'");
        }
    } else {
        //比如说案例中的内嵌属性“car.brand",当处理car时,会将获取到的car对象封装成PropertyAccessor,并缓存到nestedPropertyAccessors中
        if (logger.isTraceEnabled()) {
            logger.trace("Creating new nested " + this.getClass().getSimpleName() + " for property '" + canonicalName + "'");
        }

        nestedPa = this.newNestedPropertyAccessor(value, this.nestedPath + canonicalName + ".");
        this.copyDefaultEditorsTo(nestedPa);
        this.copyCustomEditorsTo(nestedPa, canonicalName);
        this.nestedPropertyAccessors.put(canonicalName, nestedPa);
    }

    return nestedPa;
}

执行该方法时调试结果如下图所示:

Spring IoC之BeanWrapper

在上面代码中,getPropertyNameTokens 将根据 propertyName 生成一个统一操作的结构PropertyTokenHolder,此类保存了属性名解析后的结构。我们在前文讲到 propertyName 支持三种格式,所以对应会出现三种解析后的结构。结合上述案例,

当 propertyPath 为“car.brand”,由于分割操作,会调用 getNestedPropertyAccessor()方法,进入该方法后,会执行 getPropertyNameTokens(nestedProperty)方法,此时 nestedProperty 等同于 propertyName,值为“car”。解析后的结果为:

Spring IoC之BeanWrapper

当 propertyPath 为“hobbies[1]”,程序会从 setPropertyValue(propertyName, value)方法进入到 getPropertyNameTokens(propertyName),此时 propertyName 值为“hobbies[1]”。解析后的结果为:

Spring IoC之BeanWrapper

当 propertyPath 为“relatives['Father']”,程序会从 setPropertyValue(propertyName, value)方法进入到 getPropertyNameTokens(propertyName),此时 propertyName 值为“relatives['Father']”。解析后的结果为:

Spring IoC之BeanWrapper

我们看一下 getPropertyNameTokens()方法的实现过程。

private AbstractNestablePropertyAccessor.PropertyTokenHolder getPropertyNameTokens(String propertyName) {
    String actualName = null;
    List<String> keys = new ArrayList(2);
    int searchIndex = 0;

    while(true) {
        int keyStart;
        int keyEnd;
        do {
            do {
                if (searchIndex == -1) {
                    //生成PropertyTokenHolder对象,更新actualName、canonicalName、keys内容
                    AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = new AbstractNestablePropertyAccessor.PropertyTokenHolder(actualName != null ? actualName : propertyName);
                    if (!keys.isEmpty()) {
                        tokens.canonicalName = tokens.canonicalName + "[" + StringUtils.collectionToDelimitedString(keys, "][") + "]";
                        tokens.keys = StringUtils.toStringArray(keys);
                    }

                    return tokens;
                }

                //首先判断propertyName是否为数组、列表、map等带有[]的形式,获取“[”的索引,如果是普通字符串,则keyStart值为-1
                //可能是二维数组,类似于“nums[1][1]“,所以继续取出key值
                keyStart = propertyName.indexOf("[", searchIndex);
                searchIndex = -1;
            } while(keyStart == -1);

            //计算下一个字符“]”的索引值
            keyEnd = this.getPropertyNameKeyEnd(propertyName, keyStart + "[".length());
        } while(keyEnd == -1);

        //不管是数组、列表还是map,获取那个集合的名称
        if (actualName == null) {
            actualName = propertyName.substring(0, keyStart);
        }

        String key = propertyName.substring(keyStart + "[".length(), keyEnd);
        //如果是map集合,获取key的值
        if (key.length() > 1 && key.startsWith("'") && key.endsWith("'") || key.startsWith("\"") && key.endsWith("\"")) {
            key = key.substring(1, key.length() - 1);
        }

        keys.add(key);
        searchIndex = keyEnd + "]".length();
    }
}

关于代码注释中提到的二维数组的情况,我进行测试操作,关于该部分的截图如下:

Spring IoC之BeanWrapper

getNestedPropertyAccessor()还有一句比较重要的代码:

Object value = this.getPropertyValue(tokens);

该方法根据 PropertyTokenHolder 获取指定 property 属性的实例,一起看下这段代码。

protected Object getPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens) throws BeansException {
    String propertyName = tokens.canonicalName;
    String actualName = tokens.actualName;
    // 获取PropertyHandler,内部实现是在BeanWrapperImpl类中,从cachedIntrospectionResults中取出该属性的PropertyDescriptor,
    // 然后取出属性的PropertyType, ReadMethod , WriteMethod
    AbstractNestablePropertyAccessor.PropertyHandler ph = this.getLocalPropertyHandler(actualName);
    // 属性不可读,抛异常
    if (ph != null && ph.isReadable()) {
        try {
            // 内省方式获取属性的实例
            Object value = ph.getValue();
            // 如果该属性是数组、list、set,tokens的keys是不为空的,keys将会保存需要访问的索引号,
            // 在map中,keys是一个字符串
            // 下面就是通过该索引号获取特定下标的属性值。
            if (tokens.keys != null) {
                if (value == null) {
                    if (!this.isAutoGrowNestedPaths()) {
                        throw new NullValueInNestedPathException(this.getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value of property referenced in indexed property path '" + propertyName + "': returned null");
                    }

                    value = this.setDefaultValue(new AbstractNestablePropertyAccessor.PropertyTokenHolder(tokens.actualName));
                }

                StringBuilder indexedPropertyName = new StringBuilder(tokens.actualName);

                //如果是多维数组的话,比如说二维数组nums[2][3],当要修改nums[1][2]的值时,此时经过getPropertyNameTokens方法返回的token的keys的值应该有两个,但是当传入的propertyPath不是”car.brand“形式,比如说数组、列表等,是从setPropertyValue()方法进入到getPropertyValue()方法中的,此外还会经过processKeyedProperty()和getPropertyHoldingValue()方法,尤其需要注意getPropertyHoldingValue()方法,在该方法中,它会对获得的token中的keys进行截取操作。本来二维数组获得的token中的keys应该有两个值,但是实际进入该方法时只有一个值。
                for(int i = 0; i < tokens.keys.length; ++i) {
                    String key = tokens.keys[i];
                    if (value == null) {
                        throw new NullValueInNestedPathException(this.getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value of property referenced in indexed property path '" + propertyName + "': returned null");
                    }

                    int index;
                    //根据类型进行差别处理
                    if (value.getClass().isArray()) {
                        index = Integer.parseInt(key);
                        value = this.growArrayIfNecessary(value, index, indexedPropertyName.toString());
                        value = Array.get(value, index);
                    } else if (value instanceof List) {
                        index = Integer.parseInt(key);
                        List<Object> list = (List)value;
                        this.growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);
                        value = list.get(index);
                    } else if (value instanceof Set) {
                        Set<Object> set = (Set)value;
                        int index = Integer.parseInt(key);
                        if (index < 0 || index >= set.size()) {
                            throw new InvalidPropertyException(this.getRootClass(), this.nestedPath + propertyName, "Cannot get element with index " + index + " from Set of size " + set.size() + ", accessed using property path '" + propertyName + "'");
                        }

                        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)) {
                            throw new InvalidPropertyException(this.getRootClass(), this.nestedPath + propertyName, "Property referenced in indexed property path '" + propertyName + "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");
                        }

                        Map<Object, Object> map = (Map)value;
                        Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(new int[]{0});
                        TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
                        Object convertedMapKey = this.convertIfNecessary((String)null, (Object)null, key, mapKeyType, typeDescriptor);
                        value = map.get(convertedMapKey);
                    }

                    indexedPropertyName.append("[").append(key).append("]");
                }
            }

            return value;
        } 
        ........
    }
} else {
    throw new NotReadablePropertyException(this.getRootClass(), this.nestedPath + propertyName);
}
}

除了通过 getNestedPropertyAccessor()方法进入到 getPropertyValue()方法,当传入的 propertyName 参数值为数组、集合、列表,Map 等,走的是另外一条途径,我专门画了一张流程图来展示:

Spring IoC之BeanWrapper

当测试案例中给 Man 类增加一个二维数组属性,然后进行调试,即可发现在 getPropertyValue()方法中的区别,代码如下:

@Test
public void doBeanWrapper(){
    Car car = new Car();

    Man person = new Man();
    BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(person);

    PropertyValue nameValue = new PropertyValue("name","hresh");
    PropertyValue ageValue = new PropertyValue("age",23);
    PropertyValue carValue = new PropertyValue("car",car);
    String[] hobbies = {"跑步","唱歌","看书"};
    PropertyValue hobbyValue = new PropertyValue("hobbies",hobbies);
    Map<String,Object> relatives = new HashMap<>();
    relatives.put("Father","hresh");
    relatives.put("Son","hresh");
    PropertyValue relativeValue = new PropertyValue("relatives",relatives);
    Integer[][] nums = {{1,2,3},{4,5,6}};
    PropertyValue numsValue = new PropertyValue("nums",nums);

    // beanWrapper.setPropertyValue(nameValue);
    beanWrapper.setPropertyValue("name","hresh");
    beanWrapper.setPropertyValue(ageValue);
    beanWrapper.setPropertyValue(carValue);
    beanWrapper.setPropertyValue("car.brand","东方红");
    beanWrapper.setPropertyValue(hobbyValue);
    beanWrapper.setPropertyValue("hobbies[1]","跳舞");
    beanWrapper.setPropertyValue(relativeValue);
    beanWrapper.setPropertyValue("relatives['Father']","clearLove");
    beanWrapper.setPropertyValue(numsValue);
    beanWrapper.setPropertyValue("nums[1][2]",22);

    System.out.println(person);

}

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

获取属性

BeanWrapperImpl 有两个获取属性的方法。

public Object getPropertyValue(String propertyName) throws BeansException

protected Object getPropertyValue(PropertyTokenHolder tokens)

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

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

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

其中 getPropertyAccessorForPropertyPath()方法在上一节中已经有详细的解析,它将递归获取可以访问该属性的 AbstractNestablePropertyAccessor,这里的实现类是 BeanWrapperImpl。例如 propertyName 是car.brand,那么 BeanWrapperImpl 所包裹的就是 Car 的实例。然后根据 getPropertyNameTokens()方法获取 token 值,最后就是执行 getPropertyValue(tokens),请参考前面。

参考文献

https://my.oschina.net/thinwonton/blog/1492224#h1_3

发表评论 取消回复
表情 图片 链接 代码

分享