Spring 屬性注入(三)AbstractNestablePropertyAccessor

Spring 屬性注入(三)AbstractNestablePropertyAccessor

Spring 系列目錄(http://www.javashuo.com/article/p-hskusway-em.html)html

BeanWrapper 有兩個核心的實現類java

  • AbstractNestablePropertyAccessor 提供對嵌套屬性的支持
  • BeanWrapperImpl 提供對 JavaBean 的內省功能,如 PropertyDescriptor

AbstractNestablePropertyAccessor 是 Spring 4.2 提供的功能,將原 BeanWrapperImpl 的功能抽出,這樣 BeanWrapperImpl 只提供對 JavaBean 的內省功能。算法

1、嵌套屬性

AbstractNestablePropertyAccessor 類經過其成員屬性提供了一種支持嵌套屬性的數據結構,下面是幾個核心的的屬性成員:spring

屬性類型及名稱 說明
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

2、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 有兩種狀況:app

  • 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 正是處理這種類型的屬性。ide

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

4、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);
}

5、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);
    
}

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

7、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

天天用心記錄一點點。內容也許不重要,但習慣很重要!

相關文章
相關標籤/搜索