找接收前端日期字符串的方法,在最后,中间可略过。
参数转换器:现在的springmvc使用的是Converter转换器,它可以进行大部分类型的参数转换,但是不包括string转日期类型,因为不知道string的具体格式。springmvc提供了很多的Converter,但是有些时候也可以定义自己的Converter用于字符串转换特殊实体类或者指定转换的日期类型的格式。
springmvc内置的类型参数有,HttpServletRequest HttpServletResponse HttpSession和Model/ModelMap,即这些参数不用我们手动创建,springmvc会帮我们创建好,只需注入就行了。当在方法的参数里面有这些类型的参数时,springmvc会自动完成绑定。不难理解HttpServletRequest HttpServletResponse就是每次请求的request对象与response,我们可以使用这两个对象处理请求与相应信息。HttpSession就是session会话对象。至于Model需要说明一下,Model只是一个接口实际传入的是ModelMap对象,他的作用变相理解为request的域,因为往ModelMap里面放的值最后都会添加进request的域里面。
参数解析器
handler的返回值有专门的处理器,而handler的参数也有对应的解析器,不同的参数会使用不同的解析器,解析器在处理器适配器的配置如下,以下是适配器默认使用的一些解析器:
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList();
resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(this.getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(this.getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(this.getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(this.getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
if (this.getCustomArgumentResolvers() != null) {
resolvers.addAll(this.getCustomArgumentResolvers());
}
resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
当处理器映射器返回handler后,交给处理器适配器处理,处理器适配器执行handler之前,会将handler的所有参数提取出来封装成一个参数集合,然后每个参数会使用解析器对handler的参数进行实参绑定(通过在request获取,并根据不同的参数类型使用对应的解析器),然后再执行handler的时候参数已经完成绑定。
参数解析器都要实现HandlerMethodArgumentResolver接口,最重要的是以下两个方法。
public interface HandlerMethodArgumentResolver {
//定义解析器支持的参数类型的判别条件
boolean supportsParameter(MethodParameter parameter);
//对于匹配成功的参数具体的解析操作。
@Nullable
Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception;
}
RequestParamMethodArgumentResolver
RequestParamMethodArgumentResolver是使用的比较多的参数解析器,以下是对其两个核心方法的讲解。
(1)RequestParamMethodArgumentResolver的supportsParameter(MethodParameter parameter):
该方法用于判断支持的类型,传入的MethodParameter就是一个封装了参数相关信息的方法参数对象。类型匹配如下:
仔细的话,可以发现添加参数解析器的时候添加了两个RequestParamMethodArgumentResolver对象,唯一的区别是属性useDefaultResolution为true还是false。
第一个RequestParamMethodArgumentResolver匹配@RequestParam注解的参数以及MultipartFile类型的参数。
第二个RequestParamMethodArgumentResolver匹配一些简单类型,比如String,CharSequence, Number,Date类型的参数。
总的来说,如果忽略先后顺序的话可以把两个RequestParamMethodArgumentResolver看成一个整体,支持@RequestParam注解的参数、MultipartFile类型、String,CharSequence, Number,Date类型的参数。
(2)Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
该方法主要就从request里获取参数值并封装成与handler方法的参数的同一类型的对象并返回。需要注意的是,从request里获取到string值后,是通过WebDataBinderFactory对象获取WebDataBinder,然后使用WebDataBinder将request里对应的值转换成与参数同类型的对象的。
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
//这一步主要作用就是从request里获取参数对应的值,需要注意的是这里都是string类型的
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
//使用使用WebDataBinderFactory创建WebDataBinder
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
//将String类型的值转换成于参数同类型的值,如果参数是Integer,这里就返回一个Integer的值。
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());
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
重要对象
1 WebDataBinderFactory
该对象主要用于创建WebDataBinder对象。它有个WebBindingInitializer类型的属性(该属性的值来自处理器适配器的WebBindingInitializer类型的属性值),WebDataBinderFactory使用WebBindingInitializer在创建WebDataBinder后完成WebDataBinder的初始化(主要是完成ConversionService的赋值)。
初始化WebDataBinder的ConversionService
@Override
public void initBinder(WebDataBinder binder) {
binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
if (this.directFieldAccess) {
binder.initDirectFieldAccess();
}
if (this.messageCodesResolver != null) {
binder.setMessageCodesResolver(this.messageCodesResolver);
}
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
if (this.validator != null && binder.getTarget() != null &&
this.validator.supports(binder.getTarget().getClass())) {
binder.setValidator(this.validator);
}
//将WebBindingInitializer的ConversionService传给WebDataBinder
if (this.conversionService != null) {
binder.setConversionService(this.conversionService);
}
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
propertyEditorRegistrar.registerCustomEditors(binder);
}
}
}
2 WebDataBinder
WebDataBinder对象有个SimpleTypeConverter类型的属性,最终使用的就是该类型的属性完成类型的转换。
WebDataBinder主要通过convertIfNecessary方法获取与参数对应类型的值。
3 SimpleTypeConverter
有两个重要的属性:TypeConverterDelegate类型的属性和ConversionService类型的属性。
实际调用的是TypeConverterSupport->doConvert,重点研究doConvert,经过层层递归,最重要的就是doConvert完成参数的自动类型绑定,然后在里面继续调用TypeConverterDelegate属性的convertIfNecessary方法。
@Nullable
private <T> T doConvert(@Nullable Object value,@Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam, @Nullable Field field) throws TypeMismatchException {
Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
try {
if (field != null) {
return this.typeConverterDelegate.convertIfNecessary(value, requiredType, field);
}
else {
return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam);
}
}
catch (ConverterNotFoundException | IllegalStateException ex) {
throw new ConversionNotSupportedException(value, requiredType, ex);
}
catch (ConversionException | IllegalArgumentException ex) {
throw new TypeMismatchException(value, requiredType, ex);
}
}
最终的方法,该方法返回的就是转换后的值。
@Nullable
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// Custom editor for this type?
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
// 获取ConversionService,它有个转换器集合,有很多的转换器。
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
//conversionService有很多转换器,这里判断是否有支持该类型的转换器。
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
//开始转换
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
Object convertedValue = newValue;
// Value not of required type?
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
convertedValue instanceof String) {
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) {
Class<?> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
}
if (editor == null) {
editor = findDefaultEditor(requiredType);
}
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}
boolean standardConversion = false;
if (requiredType != null) {
// Try to apply some standard type conversion rules if appropriate.
if (convertedValue != null) {
if (Object.class == requiredType) {
return (T) convertedValue;
}
else if (requiredType.isArray()) {
// Array required -> apply appropriate conversion of elements.
if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
}
else if (convertedValue instanceof Collection) {
// Convert elements to target type, if determined.
convertedValue = convertToTypedCollection(
(Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
else if (convertedValue instanceof Map) {
// Convert keys and values to respective target type, if determined.
convertedValue = convertToTypedMap(
(Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
if (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;
}
4 ConversionService(使用的实例是GenericConversionService)
前面的对象可以粗略的了解,经过重重递归,发现重要的就是该对象。
该对象有个GenericConversionService.Converters类型的属性,这个属性有个HashMap类型的属性,名为Converters,如下图所示。

private final Map<ConvertiblePair, GenericConversionService.ConvertersForPair> converters;
1)key为ConvertiblePair,可以简单的理解为源数据类型与目标数据类型,只要这两个相同,key就相等。目前spring内置的converters基本把所有类型的key都包含了进去。
2)value是GenericConversionService.ConvertersForPair类型,它内部有个GenericConverter的转换器集合,用于存储具有相同key的转换器(当我们添加的一个转换器是,它就会放在对应key下的GenericConverter集合的第一个,顺序越靠前,优先级越高)。
private static class ConvertersForPair {
private final LinkedList<GenericConverter> converters;
....
}
如下图所示,默认的map里面的GenericConversionService.ConvertersForPair有124个,然后每个GenericConversionService.ConvertersForPair里面都至少有一个Converter。
属性:

现在知道了所有的Converter所在的位置了以后,接下来就是关于知道源类型与目标类型后,如何找到与之匹配的转换器Converter。
匹配步骤:
1 通过源数据类型与目标类型的key找到对应的GenericConversionService.ConvertersForPair。
2 然后通过GenericConversionService.ConvertersForPair获取对应的Converter(这里会Converter会进一步判断是否支持(matchs)源数据类型与目标类型,如果不支持将返回null,即没有对应的Converter)。提示一下,一般通过Converter<S,T>接口实现的Converter的matchs都是true,因为Converter<S,T>与ConditionalConverter没有关系,没有对应的matchs方法,
4.1 canConvert方法
判断规则就是能通过源类型与目标类型获取到Converter就可以转换,如果不能获取,就不可以转换。
//sourceType传入参数类型,需要转换从目标类型targetType
public boolean canConvert(@Nullable TypeDescriptor sourceType,
TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
if (sourceType == null) {
return true;
} else {
//获取转换器
GenericConverter converter = this.getConverter(sourceType, targetType);
return converter != null;
}
}
4.2 getConverter方法
@Nullable
//获取converter
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
GenericConversionService.ConverterCacheKey key = new GenericConversionService.ConverterCacheKey(sourceType, targetType);
GenericConverter converter = (GenericConverter)this.converterCache.get(key);
if (converter != null) {
return converter != NO_MATCH ? converter : null;
} else {
//主要是通过该方法获取
converter = this.converters.find(sourceType, targetType);
if (converter == null) {
converter = this.getDefaultConverter(sourceType, targetType);
}
if (converter != null) {
this.converterCache.put(key, converter);
return converter;
} else {
this.converterCache.put(key, NO_MATCH);
return null;
}
}
}
4.3 GenericConversionService.Converters.find方法
这里会将源类型本身何其所有父类接口都获取,目标类型也是同样,然后按照从小到大组合成key去获取对应的convertiblePair,然后通过convertiblePair获取匹配的converter。比如源类型是String,目标类型是Date,那么获取到的可接受的类型如下:

@Nullable
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
//获取源类型可以转换成的类型(这些类型就是源类型实现的接口或者继承的类)
List<Class<?>> sourceCandidates = this.getClassHierarchy(sourceType.getType());
//获取目标类型可以转换成的类型(这些类型就是目标类型实现的接口或者继承的类)
List<Class<?>> targetCandidates = this.getClassHierarchy(targetType.getType());
Iterator var5 = sourceCandidates.iterator();
while(var5.hasNext()) {
Class<?> sourceCandidate = (Class)var5.next();
Iterator var7 = targetCandidates.iterator();
while(var7.hasNext()) {
Class<?> targetCandidate = (Class)var7.next();
ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
//判断convertiblePair 的converter集合里的object是否匹配(都有个ObjectToObjectConverter打底)
GenericConverter converter = this.getRegisteredConverter(sourceType, targetType, convertiblePair);
if (converter != null) {
return converter;
}
}
}
return null;
}
4.4 GenericConversionService.Converters.getRegisteredConverter方法
@Nullable
private GenericConverter getRegisteredConverter(TypeDescriptor sourceType, TypeDescriptor targetType, ConvertiblePair convertiblePair) {
//通过Key从converters集合里面获取convertersForPair,然后由convertersForPair获取converter
//converters里面存储的都是GenericConversionService.ConvertersForPair类型
GenericConversionService.ConvertersForPair convertersForPair = (GenericConversionService.ConvertersForPair)this.converters.get(convertiblePair);
if (convertersForPair != null) {
//进一步判断GenericConverter是否匹配(通过调用matchs),匹配成功才能返回GenericConverter
GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
if (converter != null) {
return converter;
}
}
Iterator var7 = this.globalConverters.iterator();
GenericConverter globalConverter;
do {
if (!var7.hasNext()) {
return null;
}
globalConverter = (GenericConverter)var7.next();
} while(!((ConditionalConverter)globalConverter).matches(sourceType, targetType));
return globalConverter;
}
4.5 convert方法
@Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
if (sourceType == null) {
Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
return this.handleResult((TypeDescriptor)null, targetType, this.convertNullSource((TypeDescriptor)null, targetType));
} else if (source != null && !sourceType.getObjectType().isInstance(source)) {
throw new IllegalArgumentException("Source to convert from must be an instance of [" + sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
} else {
//根据资源类型与目标类型获取converter(日期对象获取的是ObjectToObjectConverter类型的converter)
GenericConverter converter = this.getConverter(sourceType, targetType);
if (converter != null) {
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return this.handleResult(sourceType, targetType, result);
} else {
return this.handleConverterNotFound(source, sourceType, targetType);
}
}
}
接收日期字符串
最后发现converters里面本身自带一个String转Date的convertersForPair,这个convertersForPair自带一个Converter,该Converter的匹配规则(matchs)是必须使用@DateTimeFormat(pattern = "yyyy-MM-dd")指定日期参数或实体属性。使用示例:
package com.dahuici.zyb.entity;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class User {
private String name;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date date;
}
@GetMapping("/login")
public String helloword(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date){
System.out.println(date);
return "success";
}