目录

HttpMessageConverter

设计 RESTful API,通过 json 数据进行交互。那么前端传入的 json 数据如何被解析成 Java 对象作为 API入参,API 返回结果又如何将 Java 对象解析成 json 格式数据返回给前端,其实在整个数据流转过程中,HttpMessageConverter 起到了重要作用。

HTTP 请求和响应的传输是字节流,意味着浏览器和服务器通过字节流进行通信。

在报文到达SpringMVC和从SpringMVC出去,都存在一个字节流到java对象的转换问题。在SpringMVC中,它是由HttpMessageConverter来处理的。

当请求报文来到java中,它会被封装成为一个ServletInputStream的输入流,供我们读取报文。响应报文则是通过一个ServletOutputStream的输出流,来输出响应报文。

/images/spring-framework/httpmessageconverter.jpg

HttpMessageConverter

在Spring中,内置了大量的HttpMessageConverter。通过请求头信息中的MIME类型,选择相应的HttpMessageConverter。

Http Message Converters with the Spring Framework

  • ByteArrayHttpMessageConverter – converts byte arrays
  • StringHttpMessageConverter – converts Strings
  • ResourceHttpMessageConverter – converts org.springframework.core.io.Resource for any type of octet stream
  • SourceHttpMessageConverter – converts javax.xml.transform.Source
  • FormHttpMessageConverter – converts form data to/from a MultiValueMap<String, String>.
  • Jaxb2RootElementHttpMessageConverter – converts Java objects to/from XML (added only if JAXB2 is present on the classpath)
  • MappingJackson2HttpMessageConverter – converts JSON (added only if Jackson 2 is present on the classpath)
  • MappingJacksonHttpMessageConverter – converts JSON (added only if Jackson is present on the classpath)
  • AtomFeedHttpMessageConverter – converts Atom feeds (added only if Rome is present on the classpath)
  • RssChannelHttpMessageConverter – converts RSS feeds (added only if Rome is present on the classpath)

HTTP request 和 response 的转换器。该接口有只有5个方法,简单来说就是获取支持的 MediaType(application/json之类),接收到请求时判断是否能读(canRead),能读则读(read);返回结果时判断是否能写(canWrite),能写则写(write)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public interface HttpMessageConverter<T> {
  boolean canRead(Class<?> var1, @Nullable MediaType var2);

  boolean canWrite(Class<?> var1, @Nullable MediaType var2);

  List<MediaType> getSupportedMediaTypes();

  T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;

  void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}

WebMvcConfigurationSupport#addDefaultHttpMessageConverters 添加了缺省的 HttpMessageConverter

类名 支持的 JavaType 支持的 MediaType
ByteArrayHttpMessageConverter byte[] application/octet-stream, /
StringHttpMessageConverter String text/plain, /
MappingJackson2HttpMessageConverter Object application/json, application/*+json
AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml

请求与响应过程解析

1
2
3
4
@RequestMapping(value="/string", method=RequestMethod.POST)
public @ResponseBody String readString(@RequestBody String string) {
  return "Read string '" + string + "'";
}

在 Spring MVC 进入readString方法前,会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中,具体来说是使用了StringHttpMessageConverter类,它的canRead()方法返回true,然后它的read()方法会从请求中读出请求参数,绑定到readString()方法的string变量中。 当SpringMVC执行readString方法后,由于返回值标识了@ResponseBody,SpringMVC将使用StringHttpMessageConverter的write()方法,将结果作为String值写入响应报文,当然,此时canWrite()方法返回true。

数据的请求和响应都要经过 DispatcherServlet 类的 doDispatch(HttpServletRequest request, HttpServletResponse response) 方法的处理

请求调用栈

readWithMessageConverters:192, AbstractMessageConverterMethodArgumentResolver (org.springframework.web.servlet.mvc.method.annotation)
readWithMessageConverters:150, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
resolveArgument:128, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
resolveArgument:121, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)
getMethodArgumentValues:158, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:128, InvocableHandlerMethod (org.springframework.web.method.support)
 // 下面的调用栈重点关注,处理请求和返回值的分叉口就在这里
invokeAndHandle:97, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:849, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:760, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:85, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:967, DispatcherServlet (org.springframework.web.servlet)

响应调用栈

writeWithMessageConverters:224, AbstractMessageConverterMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
handleReturnValue:174, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
handleReturnValue:81, HandlerMethodReturnValueHandlerComposite (org.springframework.web.method.support)
// 分叉口
invokeAndHandle:113, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)

canRead 和 canWrite 的判断逻辑

/images/spring-framework/httpmessageconverter-can.webp

客户端 Request Header 中设置好 Content-Type(传入的数据格式)和Accept(接收的数据格式),根据配置好的MessageConverter来判断是否 canRead 或 canWrite,然后决定 response.body 的 Content-Type 的第一要素是对应的request.headers.Accept 属性的值,又叫做 MediaType。如果服务端支持这个 Accept,那么应该按照这个 Accept 来确定返回response.body 对应的格式,同时把 response.headers.Content-Type 设置成自己支持的符合那个 Accept 的 MediaType

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@EnableWebMvc
@Configuration
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        messageConverters.add(createXmlHttpMessageConverter());
        messageConverters.add(new MappingJackson2HttpMessageConverter());
    }

    private HttpMessageConverter<Object> createXmlHttpMessageConverter() {
        MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();

        XStreamMarshaller xstreamMarshaller = new XStreamMarshaller();
        xmlConverter.setMarshaller(xstreamMarshaller);
        xmlConverter.setUnmarshaller(xstreamMarshaller);

        return xmlConverter;
    } 
}

@Bean
public HttpMessageConverter<Object> createXmlHttpMessageConverter() {
    MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();

    // ...
    return xmlConverter;
}

相关

设计 RESTful API 接口的时候通常会将返回的数据封装成统一格式,通常我们会实现 ResponseBodyAdvice 接口来处理所有 API 的返回值,在真正 write 之前将数据进行统一的封装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@RestControllerAdvice()
public class CommonResultResponseAdvice implements ResponseBodyAdvice<Object> {

	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		return true;
	}

	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
			ServerHttpResponse response) {
		if (body instanceof CommonResult) {
			return body;
		}
		return new CommonResult<Object>(body);
	}
}