分为两个部分:
第一个部分,带你大致了解HttpMessageConverter是干嘛的
第二部分,讲一下converter是怎么工作的
Part1
1.基本api
requestBody-->ServletInputStream进来
responsetBody-->ServletOuputStream出去
2.作用
HttpMessageConverter的作用就是
把HTTP发送请求的报文请求体-->对象
把要返回到responseBdoy中的对象-->报文返回体
官方文档一句话概括:
HttpMessageConverter is responsible for converting from the HTTP request message to an object and converting from an object to the HTTP response body
3.接口源码
public interface HttpMessageConverter<T> {
//读取媒体类型,text/html、application/json
boolean canRead(Class<?> clazz, MediaType mediaType);
//指定可以把clazz的类型返回,按照返回的媒体类型
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;
}
这里有很多的实现类:我们来看一下
一般情况下,最常用的实现类有:
StringHttpMessageConverter、MappingJackson2HttpMessageConverter;而且springMVC会为我们默认生成一些converter
4.关于各个converter的作用,贴一个官方ref:https://docs.spring.io/spring/docs/4.3.22.RELEASE/spring-framework-reference/htmlsingle/#rest-message-conversion
5.例子:
@RequestMapping("/test")
@ResponseBodypublic String test(@RequestBody String reqBody) {
return "reqBody'" + reqBody+ "'";
}
流程:
@RequestBody注解只能有一个,当一个请求过来以后,首先进行类型判断
=>reqBody前面是String类,所以这里可以使用StringHttpMessageConverter(通过判断请求的reqBody的类型,在这里是请求头里面的content-type)
=>通过canRead()方法判断出使用哪个converter以后,直接用read方法,进行处理,拿到处理完以后的reqBody,返回到controller参数
同理
<=现在有一个Bean或者别的格式的,要返回给responseBody,还是先判断媒体类型,canWrite(),用哪个converter进行转换
<=然后用write()方法,把数据写到responseBody里面,这里用脚指头想就知道,肯定是用accept的请求头来自动写返回报文的content-type
6.实际情形
req:传json、传图片、传表单
response:返回json、返回图片
所以实际的converter并不需要很多种,根据你的业务来加
7.自定义converter
4.3.22-官方文档:
Customization of HttpMessageConverter can be achieved in Java config by overriding configureMessageConverters() if you want to replace the default converters created by Spring MVC, or by overriding extendMessageConverters() if you just want to customize them or add additional converters to the default ones.
如果想替换原来的converter,通过重写configureMessageConverters()这个方法
而如果你想添加一个自定义converter,通过重写extendMessageConverters()这个方法
8.添加Jackson的专门用于处理json的converter
我们现在要添加一个专门处理json的converter,下面来写一下
Part2
由于网上的资料,是在是鱼龙混杂,参差不齐,我们直接看官网的doc
我们从底下往上看
=>
/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that
* can read and write JSON using <a href="http://jackson.codehaus.org/">Jackson 2.x's</a> {@link ObjectMapper}.
*
* <p>This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
*
* <p>By default, this converter supports {@code application/json} and {@code application/*+json}.
* This can be overridden by setting the {@link #setSupportedMediaTypes supportedMediaTypes} property.
*
* <p>The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}.
*
* <p>Compatible with Jackson 2.1 and higher.
*
* @author Arjen Poutsma
* @author Keith Donald
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 3.1.2
*/
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter
这个converter可以用来绑定bean,也可以绑定一个hashmap
默认这个converter是支持application/json的媒体类型的
默认的构造方法,使用的是由Jackson2ObjectMapperBuilder提供的配置
继承了一个抽象类,有2个构造方法:
无参:
/**
* Construct a new {@link MappingJackson2HttpMessageConverter} using default configuration
* provided by {@link Jackson2ObjectMapperBuilder}.
*/
public MappingJackson2HttpMessageConverter() {
this(Jackson2ObjectMapperBuilder.json().build());
}
有参:
/**
* Construct a new {@link MappingJackson2HttpMessageConverter} with a custom {@link ObjectMapper}.
* You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
* @see Jackson2ObjectMapperBuilder#json()
*/
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, new MediaType("application", "json", DEFAULT_CHARSET),
new MediaType("application", "*+json", DEFAULT_CHARSET));
}
=>
我们来看一下这是什么意思:
无参的构造方法:会默认返回一个ObjectMapper,在这里,暂时先不管ObjectMapper,意思大概能才出现,是object-json的一个映射的处理类
有参的构造方法:传一个ObjectMapper进来,明显这里的ObjectMapper就是自定义的ObjectMapper,如果说上面的是默认的,下面的这个就是自定义的,而且这个自定义的mapper有一个默认的媒体类型application/json
下面还有一些方法:是关于json劫持的,这里不讲
MappingJackson2HttpMessageConverter所继承的父类AbstractJackson2HttpMessageConverter
这个父类里面有一些默认设置,比如:
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
protected ObjectMapper objectMapper;
ObjectMapper的set/get方法等
然后关于继承自HttpMessageConverter的read/write方法都是有的
现在问题来了,前端post来的requestbody的类型,如果我的converter都可以解析,用哪一个呢?现在最主要的就是这个问题,我post一个json或者任意格式的数据,那么后台用哪一个converter去接收呢?媒体类型是如何进行匹配的呢?,比如:
/**
* Implementation of {@link HttpMessageConverter} that can read and write strings.
*
* <p>By default, this converter supports all media types ({@code */*}),
* and writes with a {@code Content-Type} of {@code text/plain}. This can be overridden
* by setting the {@link #setSupportedMediaTypes supportedMediaTypes} property.
*
* @author Arjen Poutsma
* @since 3.0
*/
StringHttpMessageConverter这个converter默认可以接收任何的媒体类型,返回一个text/plain类型,想要配置自定义的媒体类型,可以写setSupportedMediaTypes
/**
* Implementation of {@link HttpMessageConverter} that can read and write byte arrays.
*
* <p>By default, this converter supports all media types ({@code */*}), and
* writes with a {@code Content-Type} of {@code application/octet-stream}. This can be
* overridden by setting the {@link #setSupportedMediaTypes supportedMediaTypes} property.
*
* @author Arjen Poutsma
* @since 3.0
*/
同样,ByteArrayHttpMessageConverter这个converter也支持所有的媒体类型,返回的content-type是一个application/octet-stream,想要配置自定义的媒体类型,同样可以写setSupportedMediaTypes
那springMVC默认用的是什么converter呢?
我们顺藤摸瓜,从
找到了RequestMappingHandlerAdapter这个类,创建了一个list集合放converter
然后往里面add了4个converter
默认有4个converter负责处理发送过来的requstBody,说明,没错,就是这里,我们接着往下看,把如何处理post来的requestBody的代码找出来
怎么找呢,有这4个converter的集合,到时候肯定要用到的
一个个找:
/**
* Provide the converters to use in argument resolvers and return value
* handlers that support reading and/or writing to the body of the
* request and response.
*/
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
"提供这些converters在‘参数解析器’里使用...返回支持关于request/response的body可支持的值"
/**
* Return the configured message body converters.
*/
public List<HttpMessageConverter<?>> getMessageConverters() {
return this.messageConverters;
}
"返回已经配置好了的converters"
再往下,我们发现几行关键代码:
用于处理"返回"的代码
用于处理"接收"的代码
然后我们发现,用于解析的resolver,全部放在了
接下来看:
RequestResponseBodyMethodProcessor,这是处理converter的核心类
这个processor是用来解析注解了@RequestBody的方法参数,并且返回了写的注解了@Responsebody的方法,这一切都是通过HttpMessageConverter来执行的
有若干个构造方法,这里不展示,我们来看它是如何解析的
==>
在这里调用了readWithMessageConverters这一句重要代码:意思就是,我们要用这些concerters怎么样去匹配requestbody,接下来直接进入这个方法里面check
RequestResponseBodyMethodProcessor继承了一个abstrat类,重写了一个父类方法,这个方法如下:
方法上面的注释是“把传来的request按照我们想要的参数类型把参数值给解析出来”
这个方法传了3个参数:当前的request、方法的参数、已经被创建的参数值的类型
返回值:我们希望返回的某类型的参数值
接下来看实现方法:
/**
* Create the method argument value of the expected parameter type by reading
* from the given HttpInputMessage.
* @param <T> the expected type of the argument value to be created
* @param inputMessage the HTTP input message representing the current request
* @param parameter the method parameter descriptor
* @param targetType the target type, not necessarily the same as the method
* parameter type, e.g. for {@code HttpEntity<String>}.
* @return the created method argument value
* @throws IOException if the reading from the request fails
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
*/
@SuppressWarnings("unchecked")
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;//声明一个媒体类型
boolean noContentType = false;
try {
contentType = inputMessage.getHeaders().getContentType();//从头部键里面拿到content-type的值
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;//如果头部键没有content-type的值,默认的content-type就是矢量图的这个媒体类型
}
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;//声明了一个空值body
EmptyBodyCheckingHttpInputMessage message;//这是个静态内部类EmptyBodyCheckingHttpInputMessage,可以解析出request的headers和body
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
//把刚才传进来的4个converter在这里遍历
for (HttpMessageConverter<?> converter : this.messageConverters) {
//每次遍历的时候拿到2个参数:这是第一个:converter的类型
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
//这是第二个:如果这个converter是调了GenericHttpMessageConverter这个接口的,那么就用多态转为GenericHttpMessageConverter
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
//如果是GenericHttpMessageConverter的实现类,那么在这里就用GenericHttpMessageConverter来实现canRead方法,如果该converter不是GenericHttpMessageConverter的实现类且targetClass不为空,那么实现canRead方法的时候,会传不一样的参数:
已经实现GenericHttpMessageConverter-------->genericConverter.canRead(targetType, contextClass, contentType)
没有实现GenericHttpMessageConverter-------->converter.canRead(targetClass, contentType)
如果post来的requestBody的媒体类型与遍历的某一个converter一致,那么就使用该converter来处理这个requestBody
★★★等于说:就是通过遍历所有的converter,如果有一个converter符合对应的媒体类型,那么就用和这个converter来处理requestBody,而再往里面找每个一个converter的支持的媒体类型,默认的几个converter全部都不支持处理application/json
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
//这里的代码涉及到一些body的处理问题,我们就不看这个了
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;
}
tips:EmptyBodyCheckingHttpInputMessage在这里,专门用来解析request中的headers和body
默认的converter的对应的媒体类型贴出来:
converter | java-type | media-type |
ByteArrayHttpMessageConverter | byte[] | public ByteArrayHttpMessageConverter() { super(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL); } |
StringHttpMessageConverter | String | public StringHttpMessageConverter(Charset defaultCharset) { super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL); } |
ResourceHttpMessageConverter | Resource | public ResourceHttpMessageConverter() { super(MediaType.ALL); this.supportsReadStreaming = true; } |
AllEncompassingFormHttpMessageConverter | Map<K, List<?>> | 这里组合了好几个converter,姑且不看吧 |
MappingJackson2HttpMessageConverter | Object | application/json, application/*+json |
关于如何在xml里面配置converter,如果你正在学习ssm,使用xml配置,只需要学会配置MappingJackson2HttpMessageConverter即可
如果你使用的springboot,请自行查找如何配置converter
在这里简单的进行了源码分析和归纳,而且经过简单的测试,没有问题,举个例子用byte[]接收任何类型的,可以收到
下次我们讲@ResponseBody和springmvc请求的json的解析