Spring的類型轉換 以前在面試中就有被問到關於spring數據綁定方面的問題,當時對它一直只是朦朦朧朧的概念,最近稍微閑下來有時間看了一下其中數據轉換相關的內容,把相應的內容做個記錄。 下麵先說明如何去用,然後再放一下個人看參數綁定源碼的一些筆記,可能由於實力不夠,有些地方說的不是很正確,如果有 ...
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
,這個內部類的作用只是封裝轉換的源類型與目標類型。
對於GenericConverter
,getConvertibleTypes
方法就返回這個轉換器支持的轉換類型(一對一,一對多,多對多都可以滿足),convert
方法和以前一樣是負責處理具體的轉換邏輯。
而且,如果你覺得對於一個轉換器來說只通過判斷源類型和目標類型是否一致來決定是否支持轉換還不夠,Spring還提供了另一個介面ConditionalGenericConverter
。
ConditionalGenericConverter
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
ConditionalGenericConverter
介面繼承了GenericConverter
和ConditionalConverter
介面,在matches
方法中就可以在源類型與目標類型已經匹配的基礎上再進行判斷是否支持轉換。
Spring官方實現ConditionalGenericConverter
介面的轉換器大多用來處理有集合或數組參與的轉換,這其中的matches
方法就用來判斷集合或數組中的元素是否能夠成功轉換。而且因為GenericConverter
與ConditionalGenericConverter
介面功能太類似,索性就直接實現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);
}
但我們使用的是另一個繼承了ConversionService
和ConverterRegistry
的介面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);
}
令人非常遺憾的是,除了通過ConversionService
的convert
直接使用,Formatter
的print
方法通過框架使用的條件比較特殊,它需要spring標簽的支持才能做到在頁面上的格式化,parse
只需要在相應欄位上打上註解即可。
寫寫代碼
說了這麼多,自然還是來點代碼更實在。
對於Converter
和ConverterFactory
以及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序列化框架,但是參數綁定這裡還是值得看一看,等到需要用的時候就可以直接拿出來用。