环境:Spring Boot 2.1.5.RELEASE 对应 Springframework 5.1.7.RELEASE
回顾
在 Spring-MVC【源码篇】请求参数和响应结果解析 一文中,提到了 Spring MVC 在进行请求处理提供了 HandlerMethodArgumentResolver(方法参数解析器)集合 和 HandlerMethodReturnValueHandler(返回结果值解析器)集合 分别对请求参数和返回结果进行数值操作设置(如:入参的类型转换,返回结果的类型转换)。
表单入参 和 JSON格式入参
在日常开发中,针对入参通常有两种接收方式:
- 表单格式接收
- JSON格式接收
在日常开发中,针对入参的处理我们可以使用注解的方式对入参格式进行类型转换。
例如:对前端的入参时间格式进行类型转换
- 表单接收使用:@DateTimeFormat
- JSON 格式接收使用:@JsonFormat
下面来看一下我们常用的参数类型转换注解的使用
表单类型入参:时间类型转换
JSON 类型入参:时间类型转换
针对这两种类型的入参,在 Spring MVC 中对应了不同的 Resolver(参数解析器)
表单入参对应的 Resolver 为:ServletModelAttributeMethodProcessor
JSON入参对应的 Resolver 为:RequestResponseBodyMethodProcessor
下面通过源码,分析一下这两个 Resolver 解析器是如何工作的
源码分析入口
以 DispatcherServlet#doDispatch 作为入口进行追踪,追到 InvocableHandlerMethod#getMethodArgumentValues 该方法。
先来看看该方法代码
public class InvocableHandlerMethod extends HandlerMethod {
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 获取所有方法参数
MethodParameter[] parameters = this.getMethodParameters();
Object[] args = new Object[parameters.length];
for(int i = 0; i < parameters.length; ++i) {
// 遍历每个方法参数,并堆每个方法参数根据 Resolver 进行解析
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
return args;
}
}
主线逻辑:
1、获取所有参数值
2、根据入参获取对应的 Resolver 进行参数解析
3、将所有参数通过 Resolver 进行处理之后,重新放入新的参数对象 args 中进行返回。
代码很直观,遍历所有的入参,然后调用 Resolvers 解析器对入参进行操作,完成之后得到解析之后的值,拼接新的参数对象 args 。然后以新的参数对象 args 作为入参进行方法调用。
以 HandlerMethodArgumentResolverComposite#resolveArgument 作为入口,分别对 表单入参 和 JSON 格式入参 进行简单的源码分析。
先来看看 HandlerMethodArgumentResolverComposite#resolveArgument 代码
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException(
"Unsupported parameter type [" + parameter.getParameterType().getName() + "]." +
" supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
}
上面的代码也很简单:根据前面入参的封装对象 MethodParameter 作为参数,获取该参数对应的 Resolver (解析器),然后调用对应的 Resolver (解析器)的 resolveArgument 方法对入参进行解析。
源码分析-表单入参类型转换(以 @DateTimeFormat 为例)
表单参数解析对应的 Resolver 为:ServletModelAttributeMethodProcessor
聚焦 ServletModelAttributeMethodProcessor#resolveArgument 而 该方法由其父类 ModelAttributeMethodProcessor#resolveArgument 实现)
查看代码:ModelAttributeMethodProcessor#resolveArgument
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter