Spring類型轉換(Converter)


Spring的類型轉換

以前在面試中就有被問到關於spring數據綁定方面的問題,當時對它一直只是朦朦朧朧的概念,最近稍微閑下來有時間看了一下其中數據轉換相關的內容,把相應的內容做個記錄。

下面先說明如何去用,然后再放一下個人看參數綁定源碼的一些筆記,可能由於實力不夠,有些地方說的不是很正確,如果有紕漏還請各位指出。

ConversionService

原生的Java是有一個可以提供數據轉換功能的工具——PropertyEditor。但是它的功能有限,它只能將字符串轉換為一個Java對象。在web項目中,如果只看與前端交互的那一部分,這個功能的確已經足夠了。但是在后台項目內部可就得重新想辦法了。

Spring針對這個問題設計了Converter模塊,它位於org.springframework.core.converter包中。該模塊足以替代原生的PropertyEditor,但是spring選擇了同時支持兩者,在Spring MVC處理參數綁定時就用到了。

該模塊的核心是ConversionService接口,內容如下:

public interface ConversionService {

	boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);

	boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

	@Nullable
	<T> T convert(@Nullable Object source, Class<T> targetType);

	@Nullable
	Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

}

接口里的方法定義的還是比較直觀的,見名知意。其中的TypeDescriptor是spring自己定義的類,它提供了獲取類型更多信息的便捷方法。比如是否含有注解、是否實現map接口、獲取map的key與value的TypeDescriptor等等。

由此可見,converter模塊不僅支持任意類型之間的轉換,而且能更簡單地獲得更多的類型信息從而做出更細致的類型轉換。

轉換器

ConversionService只是個Service,對於每個類型轉換的操作,它並不是最終的操作者,它會將相應操作交給對應類型的轉換器。而在實際項目中,由於業務復雜,對類型轉換的要求也不一樣,因此spring提供了幾個接口來方便自定義轉換器。

Converter<S, T>

接口定義如下:

@FunctionalInterface
public interface Converter<S, T> {
    @Nullable
    T convert(S var1);
}

該接口非常簡單,只定義了一個轉換方法,兩個泛型參數則是需要轉換的兩個類型。在單獨處理兩個類型的轉換時這是首選,即一對一,但是倘若有同一父類(或接口)的類型需要進行類型轉化,為每個類型都寫一個Converter顯然是十分不理智的。對於這種情況,spring提供了一個ConverterFactory接口。

ConverterFactory<S, R>

public interface ConverterFactory<S, R> {
    <T extends R> Converter<S, T> getConverter(Class<T> var1);
}

我們可以看到,該工廠方法可以生產從S類型到T類型的轉換器,而T類型必定繼承或實現R類型,我們可以形象地稱為“一對多”,因此該接口更適合實現需要轉換為同一類型的轉換器。

對於大部分需求上面兩個接口其實已經足夠了(至少我感覺是),但是不是還沒用到TypeDescriptor嗎?如果要實現更為復雜的轉換功能的話,spring提供了擁有TypeDescriptor參數的GenericConverter接口。

GenericConverter

public interface GenericConverter {
    
	@Nullable
	Set<ConvertiblePair> getConvertibleTypes();

	@Nullable
	Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

	final class ConvertiblePair {

		private final Class<?> sourceType;

		private final Class<?> targetType;

		public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
			Assert.notNull(sourceType, "Source type must not be null");
			Assert.notNull(targetType, "Target type must not be null");
			this.sourceType = sourceType;
			this.targetType = targetType;
		}

		public Class<?> getSourceType() {
			return this.sourceType;
		}

		public Class<?> getTargetType() {
			return this.targetType;
		}
        
        // 省去了一些Override方法
	}
}

GenericConverter中擁有一個內部類ConvertiblePair,這個內部類的作用只是封裝轉換的源類型與目標類型。

對於GenericConvertergetConvertibleTypes方法就返回這個轉換器支持的轉換類型(一對一,一對多,多對多都可以滿足),convert方法和以前一樣是負責處理具體的轉換邏輯。

而且,如果你覺得對於一個轉換器來說只通過判斷源類型和目標類型是否一致來決定是否支持轉換還不夠,Spring還提供了另一個接口ConditionalGenericConverter

ConditionalGenericConverter

public interface ConditionalConverter {

   boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {

}

ConditionalGenericConverter接口繼承了GenericConverterConditionalConverter接口,在matches方法中就可以在源類型與目標類型已經匹配的基礎上再進行判斷是否支持轉換。

Spring官方實現ConditionalGenericConverter接口的轉換器大多用來處理有集合或數組參與的轉換,這其中的matches方法就用來判斷集合或數組中的元素是否能夠成功轉換。而且因為GenericConverterConditionalGenericConverter接口功能太類似,索性就直接實現ConditionalGenericConverter接口了。

如何使用

那么如何使用轉換器呢,Spring要求我們要把所有需要使用轉換器注冊到ConversionService,這樣Spring在遇到類型轉換的情況時,會去ConversionService中尋找支持的轉換器,進行必要的格式轉換。

支持轉換器注冊的接口為ConverterRegistry

public interface ConverterRegistry {

	void addConverter(Converter<?, ?> converter);

	<S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);

	void addConverter(GenericConverter converter);

	void addConverterFactory(ConverterFactory<?, ?> factory);

	void removeConvertible(Class<?> sourceType, Class<?> targetType);

}

但我們使用的是另一個繼承了ConversionServiceConverterRegistry的接口ConfigurableConversionService,通過這個接口,就可以注冊自定義的轉換器了。

格式化

轉換器提供的功能是一個類型到另一個類型的單向轉換,而在web項目中,有些數據是需要經常做雙向轉換,最常見的就是日期時間了。將請求中一定格式的字符串轉換為日期類型,而在返回的相應中將日期類型再做指定格式的格式化,Spring中提供的工具就是Formatter接口。

Formatter<T>

@FunctionalInterface
public interface Printer<T> {
    String print(T object, Locale locale);
}

@FunctionalInterface
public interface Parser<T> {
    T parse(String text, Locale locale) throws ParseException;
}

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter接口中擁有兩個方法,一個是解析字符串的parse,一個是將字符串格式化的print,兩個方法都擁有Locale類型的參數,因此還可根據地區來做出相應的定制。

那么如何使用Formatter呢?由於注解的出現,大量需要在xml中的配置項都直接換為注解的方式,Formatter也是,Spring提供了AnnotationFormatterFactory這個接口。

AnnotationFormatterFactory<A extends Annotation>

public interface AnnotationFormatterFactory<A extends Annotation> {

	Set<Class<?>> getFieldTypes();

	Printer<?> getPrinter(A annotation, Class<?> fieldType);

	Parser<?> getParser(A annotation, Class<?> fieldType);
    
}

getFieldTypes方法返回的是當這些類型有A注解的時候我才會做格式化操作,getPrinter方法和getParser則分別獲取相應的對象,我們也可以直接將Formatter對象返回。

如何使用

格式化的操作,本質上來說也是類型轉換,即String => ? 和? => String。因此Spring將轉換器與格式化同質化,在代碼實現中,Formatter也是被轉換為相應的Printer轉換器和Parser轉換器,那么,Formatter也就可以注冊到ConversionService中了。

可以注冊Formatter的接口為FormatterRegistry,該接口繼承自ConverterRegistry,將它與ConversionService一起實現的類是FormattingConversionService

public interface FormatterRegistry extends ConverterRegistry {

	void addFormatter(Formatter<?> formatter);

	void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

	void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

	void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);

}

令人非常遺憾的是,除了通過ConversionServiceconvert直接使用,Formatterprint方法通過框架使用的條件比較特殊,它需要spring標簽的支持才能做到在頁面上的格式化,parse只需要在相應字段上打上注解即可。

寫寫代碼

說了這么多,自然還是來點代碼更實在。

對於ConverterConverterFactory以及Formatter,使用在SpringMVC的參數綁定上的機會會更多,所以直接在web項目里寫。而ConditionalGenericConverter接口官方實現的例子已經很豐富了,至少我沒想到什么新的需求,想要看代碼的話可以直接去看官方的源碼(比如ArrayToCollectionConverter),我就不自己寫了。

以下代碼基於SpringBoot 2.1.1,對應的SpringMVC為5.1.3,使用了lombok

@RestController
@RequestMapping("test")
public class TestController {
    @GetMapping("/index")
    public UserEntity test(UserEntity user) {
        return user;
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // 為webMVC注冊轉換器
        registry.addConverter(new String2StatusEnumConverter());
        registry.addConverterFactory(new String2EnumConverterFactory());
        registry.addFormatterForFieldAnnotation(new GenderFormatterFactory());
    }
}
@Data
@Component
public class UserEntity {

    private String username;
    private String password;

    // 加上注解的含義為使用枚舉的name字段進行枚舉的格式化,可改為id
    @GenderEnumFormat("name")
    private GenderEnum gender;

    private StatusEnum status;

}
public interface EnumInterface {
    Integer getId();
}
@Getter
@AllArgsConstructor
public enum GenderEnum implements EnumInterface {

    MALE(0, "男"),
    FEMALE(1, "女"),
    ;

    private Integer id;
    private String name;
}

@Getter
@AllArgsConstructor
public enum StatusEnum implements EnumInterface {
    ON(1, "啟用"),
    OFF(0, "停用"),
    ;

    private Integer id;
    private String name;

}
/**
 * String to StatusEnum 的轉換器
 */
public class String2StatusEnumConverter implements Converter<String, StatusEnum> {

    @Override
    public StatusEnum convert(String s) {
        // 注意,這里是通過id匹配
        for (StatusEnum e : StatusEnum.values()) {
            if (e.getId().equals(Integer.valueOf(s))) {
                return e;
            }
        }
        return null;
    }
}
/**
 * String to EnumInterface 的轉換器工廠
 */
public class String2EnumConverterFactory implements ConverterFactory<String, EnumInterface> {

    @Override
    public <T extends EnumInterface> Converter<String, T> getConverter(Class<T> targetType) {
        return new String2Enum<>(targetType);
    }

    /**
     * 轉換器
     */
    private class String2Enum<T extends EnumInterface> implements Converter<String, T> {

        private final Class<T> targetType;

        private String2Enum(Class<T> targetType) {
            this.targetType = targetType;
        }

        @Override
        public T convert(String source) {
            for (T enumConstant : targetType.getEnumConstants()) {
                if (enumConstant.getId().toString().equals(source)) {
                    return enumConstant;
                }
            }
            return null;
        }
    }
}
/**
 * 將打上注解的GenderEnum通過特定的字段轉換為枚舉
 */
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GenderEnumFormat {
    String value();
}
public class GenderFormatterFactory implements AnnotationFormatterFactory<GenderEnumFormat> {
    @Override
    public Set<Class<?>> getFieldTypes() {
        return Collections.singleton(GenderEnum.class);
    }

    @Override
    public Printer<?> getPrinter(GenderEnumFormat annotation, Class<?> fieldType) {
        return new GenderFormatter(annotation.value());
    }

    @Override
    public Parser<?> getParser(GenderEnumFormat annotation, Class<?> fieldType) {
        return new GenderFormatter(annotation.value());
    }

    final class GenderFormatter implements Formatter<GenderEnum> {
        private final String fieldName;
        private Method getter;

        private GenderFormatter(String fieldName) {
            this.fieldName = fieldName;
        }

        @Override
        public GenderEnum parse(String text, Locale locale) throws ParseException {
            if (getter == null) {
                try {
                    getter = GenderEnum.class.getMethod("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
                } catch (NoSuchMethodException e) {
                    throw new ParseException(e.getMessage(), 0);
                }
            }
            for (GenderEnum e : GenderEnum.values()) {
                try {
                    if (getter.invoke(e).equals(text)) {
                        return e;
                    }
                } catch (IllegalAccessException | InvocationTargetException e1) {
                    throw new ParseException(e1.getMessage(), 0);
                }
            }
            throw new ParseException("輸入參數有誤,不存在這樣的枚舉值:" + text, 0);
        }

        @Override
        public String print(GenderEnum object, Locale locale) {
            try {
                // 這里應該也判斷一下getter是否為null然后選擇進行初始化,但是因為print方法沒有效果所以也懶得寫了
                return getter.invoke(object).toString();
            } catch (IllegalAccessException | InvocationTargetException e) {
                return e.getMessage();
            }
        }
    }
}

源碼筆記

之前一直說類型轉換在Spring MVC的參數綁定中有用到,下面就放一下本人的一些筆記。由於實力問題有些地方也有些懵逼,也歡迎大家交流。

(看源碼的時候突然遇到IDEA無法下載源碼,搜出來的結果大致都是說更換maven版本,懶得更改就直接用maven命令下載源碼了:

mvn dependency:sources -DincludeArtifactIds=spring-webmvc

不加參數的話會默認下載全部的源碼)

public class InvocableHandlerMethod extends HandlerMethod {
        // ...
        protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
            if (ObjectUtils.isEmpty(this.getMethodParameters())) {
                return EMPTY_ARGS;
            } else {
                // 得到處理方法的方法參數
                MethodParameter[] parameters = this.getMethodParameters();
                Object[] args = new Object[parameters.length];

                for (int i = 0; i < parameters.length; ++i) {
                    MethodParameter parameter = parameters[i];
                    // 初始化,之后可以調用MethodParameter對象的getParameterName方法
                    parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                    // 如果providedArgs包含當前參數的類型就賦值
                    args[i] = findProvidedArgument(parameter, providedArgs);
                    if (args[i] == null) {
                        // resolvers包含了所有的參數解析器(HandlerMethodArgumentResolver的實現類,常見的比如RequestParamMethodArgumentResolver,PathVariableMethodArgumentResolver等,就是在參數前加的注解的處理類,有對應的注解的話就會用對應的解析器去處理參數綁定,如果沒有注解的話通常會和有ModelAttribute注解一樣使用ServletModelAttributeMethodProcessor,具體判斷在每個實現類的supportsParameter方法里)
                        if (!this.resolvers.supportsParameter(parameter)) {
                            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                        }

                        try {
                            // 使用解析器開始解析參數
                            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                        } catch (Exception var10) {
                            if (this.logger.isDebugEnabled()) {
                                String error = var10.getMessage();
                                if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
                                    this.logger.debug(formatArgumentError(parameter, error));
                                }
                            }

                            throw var10;
                        }
                    }
                }

                return args;
            }
        }
        // ...
    }


public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
    // ...
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                                        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        // 獲取paramter的信息,NamedValueInfo包含參數的名稱、是否必填、默認值,其實就是該參數在RequestParam注解中的配置
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        // 如果parameter是Optional類型,那么就產生一個指向相同參數對象但嵌套等級(nestingLevel)+1的MethodParameter
        MethodParameter nestedParameter = parameter.nestedIfOptional();
        // 先后解析配置項與SPEL表達式(即${}、#{})
        Object resolvedName = resolveStringValue(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                    "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }
        // 從請求(request)中獲取對應名稱的數據,如果非上傳文件,就相當於servlet中的request.getParameter(),另外如果有多個符合name的值會返回String[]
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                // 請求中沒有這個參數並且有默認值就將解析defaultValue后值的設為參數
                arg = resolveStringValue(namedValueInfo.defaultValue);
            } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                // 參數必填且方法的類型要求不是Optional的話拋異常
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            // 處理null值。如果參數類型(或者被Optional包裹的類型)是Boolean會轉換成false,而如果參數類型是基本類型的話會拋出異常(因為基本類型值不能為null)
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            // 如果有默認值將會把空字符串處理為默認值
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            // biner中有conversionService的實例,而conversionService中就包含着全部可用的轉換器。
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            try {
                // 開始真正的類型轉換
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            } catch (ConversionNotSupportedException ex) {
                throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());
            } catch (TypeMismatchException ex) {
                throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());

            }
        }

        // 鈎子方法,重寫這個方法的暫時只有PathVariableMethodArgumentResolver
        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

        return arg;
    }
    // ...
}
class TypeConverterDelegate {
    // ...

    /**
     * Convert the value to the required type (if necessary from a String),
     * for the specified property.
     *
     * @param propertyName   name of the property
     * @param oldValue       the previous value, if available (may be {@code null})
     * @param newValue       the proposed new value
     * @param requiredType   the type we must convert to
     *                       (or {@code null} if not known, for example in case of a collection element)
     * @param typeDescriptor the descriptor for the target property or field
     * @return the new value, possibly the result of type conversion
     * @throws IllegalArgumentException if type conversion failed
     */
    @SuppressWarnings("unchecked")
    @Nullable
    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
                                    @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
        // 在當前的流程中propertyName、oldValue為null,newValue為前台傳過來的真實參數值,requiredType為處理方法要求的類型,typeDescriptor為要求類型的描述封裝類

        // Custom editor for this type?
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

        ConversionFailedException conversionAttemptEx = null;

        // No custom editor but custom ConversionService specified?
        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
            // 上述條件成立
            // 在現在的邏輯里sourceTypeDesc必然為String的TypeDescriptor
            TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
            if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                // 可以轉換
                // canConvert實際上是嘗試獲取符合條件的GenericConverter,如果有就說明可以轉換
                // 對於String -> Integer的轉換,會先將String類型拆為 [String,Serializable,Comparable,CharSequence,Object]的類型層,Integer同樣拆為自己的類型層,之后先后遍歷每個類型來准確判斷是否存在可以轉換的轉換器
                try {
                    // 最終會調用到自定義的轉換器
                    return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                } catch (ConversionFailedException ex) {
                    // fallback to default conversion logic below
                    // 轉換失敗,暫存異常,將會執行默認的轉換邏輯
                    conversionAttemptEx = ex;
                }
            }
        }
        // 因為spring自帶了很多常見類型的轉換器,大部分都可以通過上面的轉換器完成。
        // 程序運行到這里沒有結束的話很可能說明類型是沒有定義轉換器的自定義類型或者參數格式真的不正確

        Object convertedValue = newValue;

        // Value not of required type?
        if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
            // 最后的條件為 當newValue不是requiredType的實例
            if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
                    convertedValue instanceof String) {
                // isAssignableFrom用來判斷Collection是否為requiredType的父類或者接口,或者二者是否為同一類型或接口
                TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
                if (elementTypeDesc != null) {
                    Class<?> elementType = elementTypeDesc.getType();
                    if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
                        // 相當於convertedValue.split(",")
                        convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                    }
                }
            }
            if (editor == null) {
                editor = findDefaultEditor(requiredType);
            }
            // 使用默認的editor進行轉換,不過默認的editor的轉換有可能與期望的不一致。(比如 "1,2,3,4" -> ArrayList<String>{"1,2,3,4"},結果是只有一個元素的list)
            convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
        }

        boolean standardConversion = false;

        // 加下來會根據requiredType來做出相應的轉換
        if (requiredType != null) {
            // Try to apply some standard type conversion rules if appropriate.

            if (convertedValue != null) {
                if (Object.class == requiredType) {
                    // requiredType是Object
                    return (T) convertedValue;
                } else if (requiredType.isArray()) {
                    // requiredType是數組
                    // 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) {
                    // 將convertedValue轉換為集合,內部對每個元素調用了convertIfNecessary(即本方法)
                    // Convert elements to target type, if determined.
                    convertedValue = convertToTypedCollection(
                            (Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
                    standardConversion = true;
                } else if (convertedValue instanceof Map) {
                    // 將convertedValue轉換為Map
                    // Convert keys and values to respective target type, if determined.
                    convertedValue = convertToTypedMap(
                            (Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
                    standardConversion = true;
                }
                if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
                    convertedValue = Array.get(convertedValue, 0);
                    standardConversion = true;
                }
                if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
                    // We can stringify any primitive value...
                    return (T) convertedValue.toString();
                } 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);
                        } catch (NoSuchMethodException ex) {
                            // proceed with field lookup
                            if (logger.isTraceEnabled()) {
                                logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
                            }
                        } catch (Exception ex) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
                            }
                        }
                    }
                    String trimmedValue = ((String) convertedValue).trim();
                    if (requiredType.isEnum() && trimmedValue.isEmpty()) {
                        // 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();
                }
            }

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

                // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
                StringBuilder msg = new StringBuilder();
                msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
                msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
                if (propertyName != null) {
                    msg.append(" for property '").append(propertyName).append("'");
                }
                if (editor != null) {
                    msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
                            "] returned inappropriate value of type '").append(
                            ClassUtils.getDescriptiveType(convertedValue)).append("'");
                    throw new IllegalArgumentException(msg.toString());
                } else {
                    msg.append(": no matching editors or conversion strategy found");
                    throw new IllegalStateException(msg.toString());
                }
            }
        }

        if (conversionAttemptEx != null) {
            if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
                throw conversionAttemptEx;
            }
            logger.debug("Original ConversionService attempt failed - ignored since " +
                    "PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
        }

        return (T) convertedValue;
    }
    // ...
}

最后

看源碼雖然很費時間,但是的確是能學到很多東西的,而且也能發現很多以前不知道的事情(比如RequestParam注解的name和defaultName參數是可以嵌套引用配置文件中的內容,也可以寫SPEL表達式),但其中還是有一些地方不是很清楚。

雖說現在項目都直接使用JSON做前后端交互,大部分類型轉換的任務都交給了JSON序列化框架,但是參數綁定這里還是值得看一看,等到需要用的時候就可以直接拿出來用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM