1. 程式人生 > >25--Spring建立Bean的過程(七),bean屬性填充轉換屬性值

25--Spring建立Bean的過程(七),bean屬性填充轉換屬性值

上一小節分析了Spring填充bean屬性時對bean屬性值的解析過程,接下來就是解析完之後,對屬性值的轉換,例如 <entry key="age" value="3"/>中的value,應該是解析成String呢?還是解析成int呢?

我們接上一小節的分析,在前面我們提到過,當注入如List,Set屬性,如果配置檔案中配置了value-type屬性,那麼在解析值引用的同時會將集合中的值進行轉換。其轉換過程是迴圈集合屬性並逐一轉換。為了本章的分析,將上一小節xml配置檔案中的value-type="java.lang.String"取消掉。

開啟BeanWrapperImpl類的convertForProperty方法。

public Object convertForProperty(@Nullable Object value, String propertyName) throws TypeMismatchException {
    // CachedIntrospectionResults-->快取了PropertyDescriptor集合
    CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults();
    // PropertyDescriptor-->表示JavaBean類通過儲存器匯出一個屬性。主要方法:
// 1. getReadMethod(),獲得用於讀取屬性值的方法 // 2. getWriteMethod(),獲得用於寫入屬性值的方法 PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName); if (pd == null) { throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,"No property '" + propertyName +
"' found"); } // 獲取型別轉換上下文 TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd); if (td == null) { // 如果未能從快取中獲取,則建立一個新的TypeDescriptor並快取 td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd))); } // 轉換型別 return convertForProperty(propertyName, null, value, td); }

在該方法中執行了兩個比較重要的操作,獲取PropertyDescriptor物件和TypeDescriptor物件。

  • PropertyDescriptor -->表示了JavaBean類通過儲存器匯出一個屬性,我們通過一張圖片來了解一下其作用
    PropertyDescriptor

  • TypeDescriptor–>型別轉換上下文,可以用來獲取要轉換的型別,例如前面提到的 <entry key="age" value="3"/>中的value,應該是解析成String呢?還是解析成int呢?通過TypeDescriptor就可以獲取到要轉換的具體型別。
    TypeDescriptor
    通過PropertyDescriptor物件,我們已經獲取到了可讀和可寫方法,本例中是getName和setName方法,那麼通過獲取到的方法,就可以得到方法的返回值型別,該型別就是要轉換的型別。
    來看td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd)));,先通過property(pd)方法得到Property物件,再通過得到的Property物件來建立TypeDescriptor物件,並快取至cachedIntrospectionResults物件中。我們來分析下前兩個步驟

Property物件表示了對JavaBean的描述,例如JavaBean的型別,名稱,可讀/可寫方法,註解等等。我們來看其建立過程

private Property property(PropertyDescriptor pd) {
    GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd;
    // 獲取javaBean的型別,可讀方法,可寫方法,及名稱,並建立新的Property物件
    return new Property(gpd.getBeanClass(), gpd.getReadMethod(), gpd.getWriteMethod(), gpd.getName());
}

TypeDescriptor物件表示了型別轉換上線文,表示了要從哪個型別進行轉換或者轉換到哪個型別。我們來看其建立過程

public TypeDescriptor(Property property) {
    Assert.notNull(property, "Property must not be null");
    this.resolvableType = ResolvableType.forMethodParameter(property.getMethodParameter());
    this.type = this.resolvableType.resolve(property.getType());
    this.annotatedElement = new AnnotatedElementAdapter(property.getAnnotations());
}

這樣我們就得到了了屬性值要轉換的型別。而具體的轉換工作,則是委託給了TypeConverterDelegate物件來完成,我們來看轉換的具體過程。開啟TypeConverterDelegate類的convertIfNecessary方法

public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
			@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

    // Custom editor for this type?
    // ①判斷有無自定義屬性編輯器
    PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

    ConversionFailedException conversionAttemptEx = null;

    // No custom editor but custom ConversionService specified?
    // ②判斷有無自定義ConversionService
    ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
    if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
        TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
        if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
            try {
                return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
            }
            catch (ConversionFailedException ex) {
                // fallback to default conversion logic below
                conversionAttemptEx = ex;
            }
        }
    }

    Object convertedValue = newValue;

    // Value not of required type?
    // ClassUtils.isAssignableValue(requiredType, convertedValue)-->判斷requiredType和convertedValue的class,是否相同,
    // 相同返回->true;否則返回->false
    // ③ 如果有自定義屬性編輯器或者通過解析出來的值型別與真實的值型別的class不同
    // 例如<property name="age" value="3"/>,我們需要將value轉換成int時
    if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
        if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {
            TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
            if (elementTypeDesc != null) {
                Class<?> elementType = elementTypeDesc.getType();
                if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
                    convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                }
            }
        }
        if (editor == null) {
            editor = findDefaultEditor(requiredType);
        }
        convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
    }

    boolean standardConversion = false;

    // ④執行轉換
    if (requiredType != null) {
        // Try to apply some standard type conversion rules if appropriate.
        if (convertedValue != null) {
            // Object型別
            if (Object.class == requiredType) {
                return (T) convertedValue;
            }
            // 陣列型別
            else if (requiredType.isArray()) {
                // Array required -> apply appropriate conversion of elements.
                if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
                    convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                }
                return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
            }
            // 集合型別
            else if (convertedValue instanceof Collection) {
                // Convert elements to target type, if determined.
                convertedValue = convertToTypedCollection((Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
                standardConversion = true;
            }
            // map型別
            else if (convertedValue instanceof Map) {
                // Convert keys and values to respective target type, if determined.
                convertedValue = convertToTypedMap((Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
                standardConversion = true;
            }

            // 注意:這裡是新開啟的if,不接上面的else if
            // 如果經過轉換過的值是陣列型別,且其長度只有1,那麼只取其第0個作為最終轉換值
            if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
                convertedValue = Array.get(convertedValue, 0);
                standardConversion = true;
            }
            // 如果型別是String,並且是java的基本資料型別或者包裝型別
            // 包括 boolean, byte, char, short, int, long, float, double
            // 和 Boolean, Byte, Character, Short, Integer, Long, Float, Double
            // 那麼直接呼叫toString()方法返回即可,注意convertedValue是Object型別,不是基本或包裝型別,所以是可以呼叫toString()方法的
            if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
                // We can stringify any primitive value...
                return (T) convertedValue.toString();
            }
            // 如果轉換值是String類的例項,但是我們又不能轉換為解析出來的requiredType的例項
            // 例如列舉型別值的注入
            else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
                if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
                    try {
                        Constructor<T> strCtor = requiredType.getConstructor(String.class);
                        return BeanUtils.instantiateClass(strCtor, convertedValue);
                    }
                    // 刪除logger資訊
                    catch (NoSuchMethodException ex) {}
                    catch (Exception ex) {}
                }
                String trimmedValue = ((String) convertedValue).trim();
                if (requiredType.isEnum() && "".equals(trimmedValue)) {
                    // It's an empty enum identifier: reset the enum value to null.
                    return null;
                }
                convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
                standardConversion = true;
            }
            // 數值型別
            else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
                convertedValue = NumberUtils.convertNumberToTargetClass((Number) convertedValue, (Class<Number>) requiredType);
                standardConversion = true;
            }
        }
        else {
            // convertedValue == null
            if (requiredType == Optional.class) {
                convertedValue = Optional.empty();
            }
        }

        // ⑤ 判定requiredType是否可從convertedValue轉換而來,並嘗試使用conversionService轉換,及處理轉換異常
        if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
            if (conversionAttemptEx != null) {
                // Original exception from former ConversionService call above...
                throw conversionAttemptEx;
            }
            else if (conversionService != null && typeDescriptor != null) {
                // ConversionService not tried before, probably custom editor found
                // but editor couldn't produce the required type...
                TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
                if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                    return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                }
            }

            // 到此為止,可以確定型別不匹配,無法轉換,丟擲IllegalArgumentException/IllegalStateException
            StringBuilder msg = new StringBuilder();
            msg.append("錯誤提示讓我刪了...");
            // 刪除了msg提示配置
            if (editor != null) {
                throw new IllegalArgumentException(msg.toString());
            }
            else {
                throw new IllegalStateException(msg.toString());
            }
        }
    }

    if (conversionAttemptEx != null) {
        if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
            throw conversionAttemptEx;
        }
    }

    // ⑥返回轉換值
    return (T) convertedValue;
}

該方法比較生澀難懂感覺,但是轉換步驟大致可分為以下幾步:

  • ①判斷有無自定義屬性編輯器
  • ②判斷有無自定義ConversionService
  • ③ 如果有自定義屬性編輯器或者通過解析出來的值型別與真實的值型別的class不同,則直接進行轉換
  • ④執行轉換主流程
  • ⑤ 判定requiredType是否可從convertedValue轉換而來,並嘗試使用conversionService轉換,及處理轉換異常
  • ⑥返回轉換值
    其中⑤是最關鍵最重要的步驟,我們重點分析該步驟
1.轉化陣列

Spring對集合、陣列型別的處理,都是迴圈並再次呼叫轉換方法,從而將其具體元素轉換為需要的值。

private Object convertToTypedArray(Object input, @Nullable String propertyName, Class<?> componentType) {
	// 如果是Collection例項,則將其轉換為陣列
	if (input instanceof Collection) {
		// Convert Collection elements to array elements.
		Collection<?> coll = (Collection<?>) input;
		Object result = Array.newInstance(componentType, coll.size());
		int i = 0;
		for (Iterator<?> it = coll.iterator(); it.hasNext(); i++) {
			Object value = convertIfNecessary(buildIndexedPropertyName(propertyName, i), null, it.next(), componentType);
			Array.set(result, i, value);
		}
		return result;
	}
	// 如果是陣列
	else if (input.getClass().isArray()) {
		// Convert array elements, if necessary.
		if (componentType.equals(input.getClass().getComponentType()) && !this.propertyEditorRegistry.hasCustomEditorForElement(componentType, propertyName)) {
			return input;
		}
		int arrayLength = Array.getLength(input);
		Object result = Array.newInstance(componentType, arrayLength);
		for (int i = 0; i < arrayLength; i++) {
			Object value = convertIfNecessary(buildIndexedPropertyName(propertyName, i), null, Array.get(input, i), componentType);
			Array.set(result, i, value);
		}
		return result;
	}
	// 單個數組
	else {
		// A plain value: convert it to an array with a single component.
		Object result = Array.newInstance(componentType, 1);
		Object value = convertIfNecessary(buildIndexedPropertyName(propertyName, 0), null, input, componentType);
		Array.set(result, 0, value);
		return result;
	}
}
2.轉換集合
private Collection<?> convertToTypedCollection(Collection<?> original, @Nullable String propertyName,
		Class<?> requiredType, @Nullable TypeDescriptor typeDescriptor) {

	if (!Collection.class.isAssignableFrom(requiredType)) {
		return original;
	}

	boolean approximable = CollectionFactory.isApproximableCollectionType(requiredType);
	if (!approximable && !canCreateCopy(requiredType)) {
		return original;
	}

	boolean originalAllowed = requiredType.isInstance(original);
	TypeDescriptor elementType = (typeDescriptor != null ? typeDescriptor.getElementTypeDescriptor() : null);
	if (elementType == null && originalAllowed && !this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
		return original;
	}

	Iterator<?> it;
	try {
		it = original.iterator();
	}
	catch (Throwable ex) {
		return original;
	}

	Collection<Object> convertedCopy;
	try {
		if (approximable) {
			convertedCopy = CollectionFactory.createApproximateCollection(original, original.size());
		}
		else {
			convertedCopy = (Collection<Object>)ReflectionUtils.accessibleConstructor(requiredType).newInstance();
		}
	}
	catch (Throwable ex) {
		return original;
	}

	int i = 0;
	for (; it.hasNext(); i++) {
		Object element = it.next();
		String indexedPropertyName = buildIndexedPropertyName(propertyName, i);
		Object converted