Spring類型轉換(Converter)

Spring的類型轉換

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

下面先說明如何去用,而後再放一下我的看參數綁定源碼的一些筆記,可能因爲實力不夠,有些地方說的不是很正確,若是有紕漏還請各位指出。java

ConversionService

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

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

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

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等等。shell

因而可知,converter模塊不只支持任意類型之間的轉換,並且能更簡單地得到更多的類型信息從而作出更細緻的類型轉換。後端

轉換器

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

Converter<S, T>

接口定義以下:mvc

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

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

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序列化框架,可是參數綁定這裏仍是值得看一看,等到須要用的時候就能夠直接拿出來用。

相關文章
相關標籤/搜索