HandlerMethodArgumentResolver

HandlerMethodArgumentResolver 是用来为处理器解析参数的主要用在前面讲过的 lnvocableHandler­Method 中。 每个Resolver 对应一种类型的参数, 所以我们可以从下图的继承结构图看出它的实现有很多:

HandlerMethodArgumentResolver
HandlerMethodArgumentResolver

其中HandlerMethodArgumentResolverComposite并不是一个解析器,是一个解析器的合集,可以将其他解析器放置在里面,通过暴露的方法来判断具体调用哪一个解析器,然后执行参数解析。这个类的方法很少也很简单,比较关键的两个方法

resolveArgument:解析参数

supportsParameter:遍历resolvers,判断是否可以解析传入的参数

HandlerMethodArgumentResolver 实现类一般有两种命名方式, 一种是 XXXMethodArgumentResolver, 另一种是 XXXMethodProcessor。前者表示一个参数解析器, 后者除了可以解析参数外还可以处理相应类型的返回值, 也就是同时还是后面要讲到的 HandlerMethodReturnValueHandler。 其中的 XXX 表示用于解析的参数的类型。 另外, 还有个 Adapter, 它也不是直接解析参数的, 而是用来兼容 WebArgumentResolver类型的参数解析器的适配器。

解析器

AbstractMessageConverterMethodArgumentResolver : 使用 HttpMessageConverter 解析
requestbody类型参数的基类。

--AbstractMessageConverterMethodProcessor : 定义相关工具, 添加返回值处理。

----HttpEntityMethodProcessor: 解析HttpEntityRequestEntity 类型的参数。

----RequestResponseBodyMethodProcessor : 使用HttpMessageConverter解析@RequestBody参数;处理使用@ResponseBody的返回值

--RequestPartMethodArgumentResolver : 解析使用@RequestPart注解的参数;MultipartFilePart的子类参数比如javax.servlet.http.Part 类型的参数。

AbstractNamedValueMethodArgumentResolver : 解析 namedValue 类型的参数(有name的参数, 如 cookie 、requestParam 、requestHeader 、pathVariable 等) 的基类, 主要功能有: 1.获取name ; 2.resolveDefaultValue、handleMissingValue、handleNullValue ;3.调用模板方法 resolveNamehandleResolvedValue 具体解析。

--AbstractCookieValueMethodArgumentResolver :解析使用@CookieValue注解的参数

----ServletCookieValueMethodArgumentResolver : 实现 resolveName 方法,从HttpServletRequest中解析出cookie值

---ExpressionValueMethodArgumentResolver : 解析@Value注解的参数,主要设置了 beanFactory, 并用它完成具体解析, 解析过程在父类完成。

--MatrixVariableMethodArgumentResolver :解析@MatrixVariable注解的参数,不包括map类型

--PathVariableMethodArgumentResolver :解析使用@PathVariable注解的参数,从uri中解析参数

--RequestAttributeMethodArgumentResolver :解析使用@RequestAttribute的参数

--RequestHeaderMethodArgumentResolver :解析使用@RequestHeader注解的参数,不包括map类型

--RequestParamMethodArgumentResolver :解析从request流中获取值的参数,如@RequestParamMultipartFilePart,和未使用@RequestParam注解的简单类型(intlong等)也被视为具有从参数名称派生的参数名称的请求参数

--SessionAttributeMethodArgumentResolver :解析使用@SessionAttribute注解的参数

AbstractWebArgumentResolverAdapter 使用@WebArgumentResolver的基类,用于向后兼容.适配WebArgumentResolver

--ServletWebArgumentResolverAdapter :新建NativeWebRequest,提供给父类

ErrorsMethodArgumentResolver :处理Errors子类参数

MapMethodProcessor :解析Map型参数(包括ModelMap类 型),直接返回mav­Container中的model

MatrixVariableMapMethodArgumentResolver :使用@MatrixVariable注解的map类型参数

ModelAttributeMethodProcessor :解决@ModelAttribute注解的方法参数,并处理@ModelAttribute注解的方法的返回值

--ServletModelAttributeMethodProcessor :使用ServletRequestDataBinder替代父类的WebDataBinder进行参数绑定

ModelMethodProcessor :解析model类型参数,直接返回mavContainer中的model

PathVariableMapMethodArgumentResolver :解析使用@PathVariable注解的map参数

RedirectAttributesMethodArgumentResolver :解析RedirectAttributes类型参数,新建RedirectAttributesModelMap类型的RedirectAttributes并设置到mavContainer中, 然后返回给我们的参数

RequestHeaderMapMethodArgumentResolver :解析使用@RequestHeader注解的map参数

RequestParamMapMethodArgumentResolver :解析使用@RequestParam注解的map参数,而且注解中有value的参数

ServletRequestMethodArgumentResolver :解析request相关的参数,例如WebRequest、ServletRequest、Multipart­Request、 HttpSession、Principal、Locale、Timezone、lnputStream、Reader、HttpMethod 类型和Zoneld类型的参数.

ServletResponseMethodArgumentResolver:解析ServletResponseOutputStreamWriterWriter类型的参数

SessionStatusMethodArgumentResolver :解析SessionStatus类型参数,直接返回mavContainer中的SessionStatus

UriComponentsBuilderMethodArgumentResolver :解析UriComponentsBuilder类型参数

当然这么多解析不能一个个去分析,我们只挑拣我们平常开发中最常用的几个来分析

RequestParamMethodArgumentResolver

在日常开发中我们会经常用到@RequestParam注解来完成get请求,现在我们来看下get请求中使用@RequestParam是如何被绑定的。

我们先把目光放回到ServletInvocableHandlerMethod#invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
   。。。。。省略
   }

再到invokeForRequest

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   。。。。省略
}

再到getMethodArgumentValues

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   MethodParameter[] parameters = getMethodParameters();
 //省略。。
 if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
         args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
 //省略。。
}

我们就从这开始分析这个resolvers,首先在前面分析过了在初始化的时候会把一系列的默认resolvers放置进来,HandlerMethodArgumentResolverComposite这个类里存储的就是大量的默认以及自定义的解析器集合。

他的父类接口HandlerMethodArgumentResolver仅有两个方法

//判断是否当前参数的解析
boolean supportsParameter(MethodParameter parameter);
//解析参数
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

HandlerMethodArgumentResolverComposite里实现是通过遍历所有的解析器看内置的处理器能否处理这个参数,如果全部不能则报错。

@Override
public boolean supportsParameter(MethodParameter parameter) {
   return getArgumentResolver(parameter) != null;
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
   HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
   if (result == null) {
      for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
         if (resolver.supportsParameter(parameter)) {
            result = resolver;
            this.argumentResolverCache.put(parameter, result);
            break;
         }
      }
   }
   return result;
}

判断之后就说明这个参数是可以被处理的,进入解析:

@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);
}

同样这里又做了一次判断,我觉得判断有点多余了,但是异常信息是不一样的。遍历找到一个可以解析的处理器之后到对应的解析器的方法中进行处理,这里我们使用RequestParamMethodArgumentResolver解析器处理我们的参数,我们进到这个类里看看:

RequestParamMethodArgumentResolver
RequestParamMethodArgumentResolver

可以看到又是熟悉的接口-抽象类-实现的层级结构,我们先不关注UriComponentsContributorAbstractNamedValueMethodArgumentResolver为子类定义如何执行以下操作:

  • 获取方法参数的命名值信息
  • 将名称解析为参数值
  • 需要参数值时处理缺失的参数值
  • (可选)处理解析值

默认值字符串可以包含$ {...}占位符和Spring表达式语言#{...}表达式。 为此,必须将ConfigurableBeanFactory提供给类构造函数。如果WebDataBinder与方法参数类型不匹配,则会创建一个WebDataBinder以将类型转换应用于解析的参数值。

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   //处理@RequestParam注解
   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
   MethodParameter nestedParameter = parameter.nestedIfOptional();
   //处理配置文件的配置值,如果name的值是通过配置文件配置可变
   Object resolvedName = resolveStringValue(namedValueInfo.name);
   if (resolvedName == null) {
      throw new IllegalArgumentException(
            "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
   }
//从请求中捞到入参值
   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) {
      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());
      }
   }

   handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

   return arg;
}

先看getNamedValueInfo,其实是为了拿到注解@RequestParam的信息,如name,required,defaultValue,而updateNamedValueInfo是针对如果注解里没有标明name就拿方法参数名作为name。

private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
   NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
   if (namedValueInfo == null) {
      namedValueInfo = createNamedValueInfo(parameter);
      namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
      this.namedValueInfoCache.put(parameter, namedValueInfo);
   }
   return namedValueInfo;
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
   RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
   return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
   String name = info.name;
   if (info.name.isEmpty()) {
      name = parameter.getParameterName();
      if (name == null) {
         throw new IllegalArgumentException(
               "Name for argument type [" + parameter.getNestedParameterType().getName() +
               "] not available, and parameter name information not found in class file either.");
      }
   }
   String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
   return new NamedValueInfo(name, info.required, defaultValue);
}

处理完注解的事宜,我们现在得到了一个NamedValueInfo,表示我们的参数名,以及默认值还有必填性,我们现在需要拿这个参数名去请求里找到传入的数据,然后绑定到参数上,在某些情况下我们使用@RequestParam注解的name配置了可变,并将值配置在配置文件里,那么就需要先把这个值替换成真正的name再去寻找参数,如果没有就是原本的name,我们接着往下看重头戏:

Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);

这是一个抽象方法,子类RequestParamMethodArgumentResolver实现代码:

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

   if (servletRequest != null) {
   //判断是否是MultipartArgument
      Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
      if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
         return mpArg;
      }
   }
//尝试提取请求中的multipart文件
   Object arg = null;
   MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
   if (multipartRequest != null) {
      List<MultipartFile> files = multipartRequest.getFiles(name);
      if (!files.isEmpty()) {
         arg = (files.size() == 1 ? files.get(0) : files);
      }
   }
   //如果没有再从请求中根据name获取参数值
   if (arg == null) {
      String[] paramValues = request.getParameterValues(name);
      if (paramValues != null) {
         arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
      }
   }
   return arg;
}

忽略掉上传文件的部分,看起来获取值很简单,我们通过Tomcat的门面类轻松地拿到了参数值,但是到这里远没有结束,接收是通过数组接收,这时参数的类型其实还是不确定的,也就是还没真正做到参数的绑定!

所以接下去开始要进行真正的类型绑定了,跳过参数为null为空的那段判断,先判断binderFactory是否为null,这个参数在RequestMappingHandlerAdapter#invokeHandlerMethod其实就已经被设置了:

invocableMethod.setDataBinderFactory(binderFactory);

所以这里的binderFactory的类型是ServletRequestDataBinderFactory,类结构图如下

ServletRequestDataBinderFactory
ServletRequestDataBinderFactory

@Override
@SuppressWarnings("deprecation")
public final WebDataBinder createBinder(
      NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {

   WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
   //初始化工作
   if (this.initializer != null) {
      this.initializer.initBinder(dataBinder, webRequest);
   }
   //处理@InitBinder注解 模板方法
   initBinder(dataBinder, webRequest);
   return dataBinder;
}

子类InitBinderDataBinderFactory实现了这个方法,还记得我们在前面捞取了@InitBinder注解的方法并存到binderMethods吗,这里就在执行这些方法,这里将我们创建的dataBinder实例传递进去进行注册我们自定义的CustomEditor。我们平时可能会需要自定义一些参数绑定例如:

@InitBinder
public void initBinder(WebDataBinder binder) {
   binder.registerCustomEditor(List.class, "extendStr", new CustomJsonEditor(List.class));
}

这里只是举了个简单的使用示例,注册了CustomEditor,当在参数绑定的时候会将符合的参数进行绑定,当然了一个controller里可能存在参数名称重复的情况,所以这时候@InitBinder注解的value就可以发挥作用,你可以指定绑定到哪一个对象。例如@InitBinder("studentReq")诸如此类的用法,下面是这个初始化绑定对象的方法的源码,invokeForRequest方法在前面分析HandlerAdapt的时候提到过这里不再做分析。

@Override
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
   for (InvocableHandlerMethod binderMethod : this.binderMethods) {
      if (isBinderMethodApplicable(binderMethod, dataBinder)) {
         Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
         if (returnValue != null) {
            throw new IllegalStateException(
                  "@InitBinder methods must not return a value (should be void): " + binderMethod);
         }
      }
   }
}

完成dataBinder的初始化之后就要准备真正的数据类型绑定

binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
@Override
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
      @Nullable MethodParameter methodParam) throws TypeMismatchException {

   return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}

找到对应的类型的转换器,这里的转换器是SimpleTypeConverter,一直往下走,到TypeConverterDelegate#convertIfNecessary

    @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

// 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) {
   TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
   if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
      try {
         //类型转换
         return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
      }
      catch (ConversionFailedException ex) {
         // fallback to default conversion logic below
         conversionAttemptEx = ex;
      }
   }
}
。。。。。省略
}

conversionService内置了大量的预定义的转换,String转int在预定义的转换中,所以直接进行了转换。完成参数绑定后我们回到AbstractNamedValueMethodArgumentResolver#resolveArgument方法中发现了方法之后还执行handleResolvedValue,这是一个空方法,交由子类覆写,但是当前的处理器并没有覆写这个方法,至此参数的绑定成功完成。

ServletModelAttributeMethodProcessor

我们这里用一个刻意的例子描述我们另一个常用的对象绑定方式:

@GetMapping(value = "/delete")
public void delete(Student student) {
   //do nothing

}

请求URL:http://localhost:8081/student/get-student?id=1&name=小米,这样子Spring也是刻意很聪明地帮助我们绑定到对象里,接下去就看看这个方式的参数解析以及绑定吧!

首先需要知道是哪一个解析器在处理,很自然断点到HandlerMethodArgumentResolverComposite#resolveArgument查看具体过滤出来的处理器,原来是ServletModelAttributeMethodProcessor

ServletModelAttributeMethodProcessor
ServletModelAttributeMethodProcessor

在前面提到过Processor是用来处理返回值的,这里以Processor命名说明既可以处理入参也可以处理结果,它的父类ModelAttributeMethodProcessor是用来支持@ModelAttribute注解的方法参数,这里代码进行复用用来绑定参数到对象上,直接看它的resolveArgument方法:

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   //参数名称,即要将参数绑定到的bean名称
   String name = ModelFactory.getNameForParameter(parameter);
   //这里暂不关心这个注解
   ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
   if (ann != null) {
      mavContainer.setBinding(name, ann.binding());
   }

   Object attribute = null;
   BindingResult bindingResult = null;

   if (mavContainer.containsAttribute(name)) {
      attribute = mavContainer.getModel().get(name);
   }
   else {
      // Create attribute instance
      //子类覆写这个方法来创建bean
      try {
         attribute = createAttribute(name, parameter, binderFactory, webRequest);
      }
      catch (BindException ex) {
         if (isBindExceptionRequired(parameter)) {
            // No BindingResult parameter -> fail with BindException
            throw ex;
         }
         // Otherwise, expose null/empty value and associated BindingResult
         if (parameter.getParameterType() == Optional.class) {
            attribute = Optional.empty();
         }
         bindingResult = ex.getBindingResult();
      }
   }

   if (bindingResult == null) {
      // Bean property binding and validation;
      // skipped in case of binding failure on construction.
      //创建并初始化DataBinder,同上一个解析器的分析
      WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
      if (binder.getTarget() != null) {
         if (!mavContainer.isBindingDisabled(name)) {
            //参数绑定到对象上
            bindRequestParameters(binder, webRequest);
         }
         //参数校验
         validateIfApplicable(binder, parameter);
         if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
            throw new BindException(binder.getBindingResult());
         }
      }
      // Value type adaptation, also covering java.util.Optional
      if (!parameter.getParameterType().isInstance(attribute)) {
         attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
      }
      bindingResult = binder.getBindingResult();
   }

   // Add resolved attribute and BindingResult at the end of the model
   Map<String, Object> bindingResultModel = bindingResult.getModel();
   mavContainer.removeAttributes(bindingResultModel);
   mavContainer.addAllAttributes(bindingResultModel);

   return attribute;
}
@Override
protected final Object createAttribute(String attributeName, MethodParameter parameter,
      WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
   //试图从请求里找到这个名称的值
   String value = getRequestValueForAttribute(attributeName, request);
   if (value != null) {
      Object attribute = createAttributeFromRequestValue(
            value, attributeName, parameter, binderFactory, request);
      if (attribute != null) {
         return attribute;
      }
   }
   //找不到就创建这个名称的bean
   return super.createAttribute(attributeName, parameter, binderFactory, request);
}
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
   ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
   Assert.state(servletRequest != null, "No ServletRequest");
   ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
   //绑定的方法
   servletBinder.bind(servletRequest);
}

在上面的分析中,我们知道了这个解析器的处理流程:先试图从请求中找到有没有一样的参数名称,找不到就通过Spring的工具方法创建一个bean,但是值都是null,这时候就需要将请求中的数据绑定到bean上,所以就在ServletModelAttributeMethodProcessor#bindRequestParameters深入下去看,我们不具体贴代码,调用层级十分多,只需要了解它的整个原理,bean被包装进BeanWrapper,通过BeanWrapperImpl内实现Java内省机制来完成赋值操作,可以参见BeanWrapperImpl.BeanPropertyHandler#setValue

    @Override
   public void setValue(final @Nullable Object value) throws Exception {
   //拿到set方法执行
      final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
            ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
            this.pd.getWriteMethod());
      if (System.getSecurityManager() != null) {
         AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            ReflectionUtils.makeAccessible(writeMethod);
            return null;
         });
         try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
                  writeMethod.invoke(getWrappedInstance(), value), acc);
         }
         catch (PrivilegedActionException ex) {
            throw ex.getException();
         }
      }
      else {
         ReflectionUtils.makeAccessible(writeMethod);
         //执行
         writeMethod.invoke(getWrappedInstance(), value);
      }
   }
}

完后set方法的执行,就完成了入参的真正绑定到对象的过程,也许参数上面有需要校验的注解,那么再执行validateIfApplicable方法进行校验,这里不做展开,至此完成了参数映射成对象的绑定。

RequestResponseBodyMethodProcessor

在前后端分离的框架中还有最常见的一种Post类型的请求,将参数以JSON串放置在body中传递到后端,使用@RequestBody注解接收的方法。

同样这也是一个返回结果与入参解析器共用的处理器。

RequestResponseBodyMethodProcessor
RequestResponseBodyMethodProcessor

这个处理器层级就多了一点,首先了解一下它的两个抽象父类的作用

AbstractMessageConverterMethodArgumentResolver:通过使用HttpMessageConverters从请求的BODY中读取来解析方法参数值的基类。

AbstractMessageConverterMethodProcessor:通过使用HttpMessageConverters写入response来扩展AbstractMessageConverterMethodArgumentResolver的能力,以处理方法返回值

这下明白了,一个为入参服务一个为返回值服务

两个抽象类都没实现抽象方法resolveArgument,而留给了RequestResponseBodyMethodProcessor实现,所以直接定位到这个类里的这个方法:

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

   parameter = parameter.nestedIfOptional();
    // 通过HttpMessageConverter读取HTTP报文
   Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
   String name = Conventions.getVariableNameForParameter(parameter);

   if (binderFactory != null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
      if (arg != null) {
       // 这里完成数据绑定+数据校验(绑定的错误和校验的错误都会放进Errors里)
         validateIfApplicable(binder, parameter);
         if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
            throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
         }
      }
       // 把错误消息放进去
       // 后续逻辑会判断MODEL_KEY_PREFIX这个key
      if (mavContainer != null) {
         mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
      }
   }

   return adaptArgumentIfNecessary(arg, parameter);
}

先看关键的readWithMessageConverters方法,通过HttpMessageConverter读取参数

@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
      Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

   HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
   Assert.state(servletRequest != null, "No HttpServletRequest");
   ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
 //继续往下看readWithMessageConverters方法
   Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
   if (arg == null && checkRequired(parameter)) {
      throw new HttpMessageNotReadableException("Required request body is missing: " +
            parameter.getExecutable().toGenericString(), inputMessage);
   }
   return arg;
}

到了父类AbstractMessageConverterMethodArgumentResolver

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
      Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

//处理contentType
   MediaType contentType;
   boolean noContentType = false;
   try {
      contentType = inputMessage.getHeaders().getContentType();
   }
   catch (InvalidMediaTypeException ex) {
      throw new HttpMediaTypeNotSupportedException(ex.getMessage());
   }
   if (contentType == null) {
      noContentType = true;
      contentType = MediaType.APPLICATION_OCTET_STREAM;
   }

   Class<?> contextClass = parameter.getContainingClass();
   Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
   if (targetClass == null) {
      ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
      targetClass = (Class<T>) resolvableType.resolve();
   }

   HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
   Object body = NO_VALUE;

   EmptyBodyCheckingHttpInputMessage message;
   try {
      message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

      for (HttpMessageConverter<?> converter : this.messageConverters) {
         Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
         GenericHttpMessageConverter<?> genericConverter =
               (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
         if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
               (targetClass != null && converter.canRead(targetClass, contentType))) {
            if (message.hasBody()) {
               HttpInputMessage msgToUse =
                     getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
               body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                     ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
               body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
            }
            else {
               body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
            }
            break;
         }
      }
   }
   catch (IOException ex) {
      throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
   }

   if (body == NO_VALUE) {
      if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
            (noContentType && !message.hasBody())) {
         return null;
      }
      throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
   }

   MediaType selectedContentType = contentType;
   Object theBody = body;
   LogFormatUtils.traceDebug(logger, traceOn -> {
      String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
      return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
   });

   return body;
}

首先需要处理contentType,因为contentType关系到要用哪个converter来处理body的数据,如果你有自己制定contentType,那么以你为准否则使用默认的MediaType.APPLICATION_OCTET_STREAM,接下去拿到HttpMethod,在body没拿到数据的时候进行判断,到底是没数据传进来还是是一个异常的请求,这是后面的代码的事,在拿到HttpMethod之后就要开始关键的处理,假定我们的contentType是application/json,那么就是将JSON转成对象,遍历messageConverters找到能够处理JSON的转换器,也许你已经忘了messageConverters是怎么被注册进来的,我们来回顾一下messageConverters的注册过程:

这里我们假定你是通过@EnableWebMvc+实现WebMvcConfigurer接口完成的自定义的配置:

@EnableWebMvc注解引入了DelegatingWebMvcConfiguration配置类,他的父类WebMvcConfigurationSupport注册了RequestMappingHandlerAdapter的bean

@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,
      @Qualifier("mvcValidator") Validator validator) {

   RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
   adapter.setContentNegotiationManager(contentNegotiationManager);
   //这里传入MessageConverters
   adapter.setMessageConverters(getMessageConverters());
   。。。。省略
   }

getMessageConverters方法中,当messageConvertersnull的时候,初始化了一个空List准备用来装转换器,并先预留了一个抽象方法configureMessageConverters给子类实现

protected final List<HttpMessageConverter<?>> getMessageConverters() {
   if (this.messageConverters == null) {
      this.messageConverters = new ArrayList<>();
      //抽象方法
      configureMessageConverters(this.messageConverters);
      if (this.messageConverters.isEmpty()) {
         addDefaultHttpMessageConverters(this.messageConverters);
      }
      //额外的消息转换器,可以在默认的转换器基础上进行添加
      extendMessageConverters(this.messageConverters);
   }
   return this.messageConverters;
}

子类DelegatingWebMvcConfigurationsetConfigurers方法会先把我们实现WebMvcConfigurer接口做的一些自定义配置类添加到WebMvcConfigurerComposite中:

private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
   if (!CollectionUtils.isEmpty(configurers)) {
      this.configurers.addWebMvcConfigurers(configurers);
   }
}

接下去看configureMessageConverters方法的实现,发现会在这里会处理WebMvcConfigurer的实现类里我们所添加的MessageConverters

@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
   this.configurers.configureMessageConverters(converters);
}

但是这个方法是覆盖性的,一旦添加了自定义的转换器,默认的转换器就不会被添加进去,所以Spring很贴心地给你提供了extendMessageConverters,既能够保留默认的转换器又能添加自定义的,具有很强的灵活性!

创建RequestMappingHandlerAdapter bean的时候会触发afterPropertiesSet方法会注册一系列的默认的解析器,这时候我们所添加的消息转换器就这样被注册到当前的Processor里去了。

说完this.messageConverters的来由之后,找到合适的转换器之后,先要处理RequestResponseBodyAdvice这个切面的beforeBodyRead方法,转换body里的内容转成对象之后,还要再处理一次这个切面,看看切面是不是也要对转换后的对象做一些特殊的操作,调用的是afterBodyRead这个是由我们实现的,么有就跳过了,这部分的逻辑算是简单易懂。

当我们将body里的数据处理成对象之后,接下去同样的创建binder,把对象放进去,完成参数校验,之后还有个adaptArgumentIfNecessary就是做了一个简单的适配转换,没什么可讲,基本上我们也不会用Optional来包裹入参,这是极不推荐的一个做法。

小结

至此我们分析了三个常见的入参解析器

RequestParamMethodArgumentResolver:处理@RequestParam注解的参数

ServletModelAttributeMethodProcessor:处理get请求的参数并将其绑定到bean上

RequestResponseBodyMethodProcessor:处理@RequestBody注解的参数,从body获取数据解析