SpringMVC对HTTP报文体的处理
客户端和服务端HTTP报文传递消息,而HTTP报文包含报文头和报文体。通常,解析请求参数以及返回页面都不需要我们关心HTTP报文体的读取和生成过程。但在某些特定场景下需要直接到请求报文中读取报文体,或者将返回的数据直接写入到报文体中。
在SpringMVC中,可以利用RequestBody注解表示一个参数,说明解析它需要读取报文体,也可以直接将参数类型声明成HttpEntity<T>类型。与处理请求类似,如果想要将对象写入到响应报文的报文体中,则可以返回HttpEntity<T>类型的数据或者在方法上标注ResponseBody。
RequestBody与ResponseBody对应的处理器是RequestResponseBodyMethodProcessor,它同时负责消息体的读取和写入,它既是参数解析器也是结果处理器。下面看看它读取和写入报文体的逻辑:
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation annot : annotations) {
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
Object hints = AnnotationUtils.getValue(annot);
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
}
}
return arg;
}
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
mavContainer.setRequestHandled(true);
if (returnValue != null) {
writeWithMessageConverters(returnValue, returnType, webRequest);
}
}
由上面的两个方法可以看出,不管是消息体的读取还是写入都涉及到了HttpMessageConverter,这个对象就是spring负责消息体与Java对象互相转换的工具。另外, HttpEntity类型的数据处理器为HttpEntityMethodProcessor,它对消息体的读取和写入也是利用HttpMessageConverter:
public Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws IOException, HttpMediaTypeNotSupportedException {
HttpInputMessage inputMessage = createInputMessage(webRequest);
Class<?> paramType = getHttpEntityType(parameter);
Object body = readWithMessageConverters(webRequest, parameter, paramType);
return new HttpEntity<Object>(body, inputMessage.getHeaders());
}
public void handleReturnValue(
Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws Exception {
mavContainer.setRequestHandled(true);
if (returnValue == null) {
return;
}
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
Assert.isInstanceOf(HttpEntity.class, returnValue);
HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
if (responseEntity instanceof ResponseEntity) {
outputMessage.setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
}
HttpHeaders entityHeaders = responseEntity.getHeaders();
if (!entityHeaders.isEmpty()) {
outputMessage.getHeaders().putAll(entityHeaders);
}
Object body = responseEntity.getBody();
if (body != null) {
writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
}
else {
// flush headers to the HttpServletResponse
outputMessage.getBody();
}
}
既然 HttpMessageConverter这么重要,就来看看它的接口定义:
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
每个
HttpMessageConverter都要实现canRead和canWrite方法,以此来判断当前的HttpMessageConverter是否可以胜任此次报文体处理的任务。MediaType 对应的HTTP的Content-Type头部,只有同时符合数据类型以及Content-Type的描述才可以正确的读取或者写入报文体。与参数解析器和结果处理器一样,HttpMessageConverter的设计也采用了策略模式,也是配置在HandlerAdapter中。
在实际开发,若需要跟客户端实现自己的协议,则可以自己实现 HttpMessageConverter并配置到HandlerAdapter中。