SpringMvc源码分析5.2——HandlerMethodArgumentResolver


接着上一篇,我们来探究一下RequestResponseBodyMethodProcessor

一. 类实现图

AbstractMessageConverterMethodArgumentResolver

回顾一下HandlerMethodArgumentResolver的两个方法:

public interface HandlerMethodArgumentResolver {
   boolean supportsParameter(MethodParameter parameter);
   
   Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
         NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

二. 源码分析

我们首先从抽象类AbstractMessageConverterMethodArgumentResolver看起,

1. AbstractMessageConverterMethodArgumentResolver

通过使用HttpMessageConverter从请求的主体读取内容来解析方法参数值的基类

该抽象类里面包含了一些属性:

// 支持的http方法
private static final Set<HttpMethod> SUPPORTED_METHODS =
			EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);
// 消息转换器(重要)
protected final List<HttpMessageConverter<?>> messageConverters;
// 支持的MediaType
protected final List<MediaType> allSupportedMediaTypes;
// 这个的作用是在GenericHttpMessageConverter.read前后做一些自定义处理
// 我们可以实现RequestBodyAdvice或者ResponseBodyAdvice并标注@ControllerAdvice,从而自定义一些处理
private final RequestResponseBodyAdviceChain advice;

该抽象类定义一些供子类使用的方法。如readWithMessageConverters(..)HttpInputMessage中读取并创建期待的参数值,还有validateIfApplicable(..)参数值的校验等。

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

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


protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
		Annotation[] annotations = parameter.getParameterAnnotations();
		for (Annotation ann : annotations) {
			Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
			if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
				Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
				Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
				binder.validate(validationHints);
				break;
			}
		}
	}

2. RequestPartMethodArgumentResolver

该参数解析器支持@RequestPart,处理文件的上传。可以与@RequestParam处理上传做一个对比。这两个注解处理文件上传用的不是一个参数解析器。

3. AbstractMessageConverterMethodProcessor

拓展了AbstractMessageConverterMethodArgumentResolver使其能够使用HttpMessageConverters处理方法的返回。多实现了一个HandlerMethodReturnValueHandler接口。

这个类里面主要封装了一些对handler返回值的写出,返回值的处理我们暂不说明。

4. RequestResponseBodyMethodProcessor

支持@RequestBody参数的处理,参数的解析核心还是调用RequestResponseBodyMethodProcessor#readWithMessageConverters(..)

5. HttpEntityMethodProcessor

支持HttpEntity或者RequestEntity参数的处理,参数的解析核心还是调用RequestResponseBodyMethodProcessor#readWithMessageConverters(..)

整个下来,发现,核心还是在RequestResponseBodyMethodProcessor#readWithMessageConverters(..)

那我们直接看一下该方法readWithMessageConverters(..)关键的解析body的一段:

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()) {
              // RequestBodyAdvice前置处理
              HttpInputMessage msgToUse =
                    getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
              // 消息转换
              body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                    ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
             //  RequestBodyAdvice后置处理
             body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
           }
           else {
              body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
           }
           break;
        }
     }
  }

这里面涉及到了HttpMessageConverterRequestResponseBodyAdviceChain这两个主要类。找到匹配的HttpMessageConverter读取HttpInputMessage。在之前我们其实debug看到过spring容器里面这些实例的值。咱们再次debug查看一下吧,先写一个能被该参数解析器处理的接口(这不是必须的):

@PostMapping("test")
@ResponseBody
public String test(@RequestBody User user) {
  System.out.println(user);
  return "ok";
}

利用curl 命令请求:

curl -H "Content-Type:application/json" -X POST --data '{"username":"jack"}' http://localhost:8080/test

可以看到这两个关键对象的值。

Xnip2020-02-27_16-13-35

我们继续debug,可以发现处理我们/test请求的HttpMessageConverterMappingJackson2HttpMessageConverter,是利用jackson来将我们的json串转为User 对象的。

在这里这个advice类并没有起到作用,因为没有找到匹配的RequestBodyAdvice。截图里面的JsonViewRequestBodyAdvice需要参数上带有@JsonView注解才能处理。

@JsonViewjackson提供的一个注解,作用是可以按需过滤对象属性,可以参考Jackson Annotations(二)。其实我们可以参考JsonViewRequestBodyAdvice来实现一些功能,比如:某个参数乃至整个请求体的加解密,使我们避免重复的样板代码。

如果我们想要在消息转换前后实现自己的advice逻辑,我们可以在springboot如下实现:

// 1. 标注注解 2. 继承RequestBodyAdviceAdapter或者RequestBodyAdvice
@ControllerAdvice
public class RequestBodyAdviceExample extends RequestBodyAdviceAdapter {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // todo
        return false;
    }
  
   // override其他方法
}

此时,再次debug查看,可以看到我们自定义的advice已经注入。

Xnip2020-02-27_17-12-34

三. 总结

  1. 主要讲解了一下AbstractMessageConverterMethodArgumentResolver类型的参数解析器,从请求体里面获取消息并转换;
  2. AbstractMessageConverterMethodArgumentResolver会利用HttpMessageConverter去转换http消息体里面的消息;
  3. RequestBodyAdvice的作用,以及怎么实现自己的RequestBodyAdvice逻辑;

文章作者: shiv
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 shiv !
评论
  目录