接着上一篇,我们来探究一下
RequestResponseBodyMethodProcessor
。
一. 类实现图
回顾一下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;
}
}
}
这里面涉及到了HttpMessageConverter
和RequestResponseBodyAdviceChain
这两个主要类。找到匹配的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
可以看到这两个关键对象的值。
我们继续debug,可以发现处理我们/test
请求的HttpMessageConverter
为MappingJackson2HttpMessageConverter
,是利用jackson
来将我们的json
串转为User
对象的。
在这里这个advice
类并没有起到作用,因为没有找到匹配的RequestBodyAdvice
。截图里面的JsonViewRequestBodyAdvice
需要参数上带有@JsonView
注解才能处理。
@JsonView
是jackson
提供的一个注解,作用是可以按需过滤对象属性,可以参考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
已经注入。
三. 总结
- 主要讲解了一下
AbstractMessageConverterMethodArgumentResolver
类型的参数解析器,从请求体里面获取消息并转换; AbstractMessageConverterMethodArgumentResolver
会利用HttpMessageConverter
去转换http
消息体里面的消息;RequestBodyAdvice
的作用,以及怎么实现自己的RequestBodyAdvice
逻辑;