zoukankan      html  css  js  c++  java
  • 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 分以下步骤:

    1. 递归获取 propertyName 属性所在的 beanWrapper,如果这个属性为 null 则是否创建一个默认的值(setDefaultValue)?
    2. 根据属性的 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);
        }
    }
    

    参考:

    1. 《Spring 学习 (二)BeanWrapper及其实现》:https://blog.csdn.net/zhiweianran/article/details/7919129

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    Android 编辑框(EditText)属性学习
    读书笔记 -《高效程序猿的45个习惯-敏捷开发修炼之道》
    Launcher3实现壁纸居中
    Trie树的常见应用大总结(面试+附代码实现)
    LeetCode Summary Ranges
    abap选择屏幕上的button
    SQLite Expert表分离和解决SQLite Expert删除表后大小不变的问题
    git push 失败
    动态限制EdiText仅仅能输入特定字符
    DateTime类常用技巧摘录
  • 原文地址:https://www.cnblogs.com/binarylei/p/10267928.html
Copyright © 2011-2022 走看看