之前在面试中就有被问到关于spring数据绑定方面的问题,当时对它一直只是朦朦胧胧的概念,最近稍微闲下来有时间看了一下其中数据转换相关的内容,把相应的内容作个记录。前端
下面先说明如何去用,而后再放一下我的看参数绑定源码的一些笔记,可能因为实力不够,有些地方说的不是很正确,若是有纰漏还请各位指出。java
原生的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提供了几个接口来方便自定义转换器。数组
接口定义以下:mvc
@FunctionalInterface public interface Converter<S, T> { @Nullable T convert(S var1); }
该接口很是简单,只定义了一个转换方法,两个泛型参数则是须要转换的两个类型。在单独处理两个类型的转换时这是首选,即一对一,可是假若有同一父类(或接口)的类型须要进行类型转化,为每一个类型都写一个Converter显然是十分不理智的。对于这种状况,spring提供了一个ConverterFactory
接口。app
public interface ConverterFactory<S, R> { <T extends R> Converter<S, T> getConverter(Class<T> var1); }
咱们能够看到,该工厂方法能够生产从S类型到T类型的转换器,而T类型一定继承或实现R类型,咱们能够形象地称为“一对多”,所以该接口更适合实现须要转换为同一类型的转换器。
对于大部分需求上面两个接口其实已经足够了(至少我感受是),可是不是还没用到TypeDescriptor
吗?若是要实现更为复杂的转换功能的话,spring提供了拥有TypeDescriptor
参数的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
。
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
接口。
@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
这个接口。
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序列化框架,可是参数绑定这里仍是值得看一看,等到须要用的时候就能够直接拿出来用。