背景知识
- Content-Type必须是具体确定的类型,不能包含 *;
- Accept匹配规则:最明确的优先匹配;
- @RequestMapping:consumes属性指定处理请求的提交内容类型(Content-Type),produces属性指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
HttpMessageConverters
作用:Http请求/响应与Java对象之间的转换,如下图所示。
请求转换过程:readWithMessageConverters
首先,从请求头中获取content-type,如下:
MediaType contentType;
try {
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
其次,依次遍历HttpMessageConverter,判断其是否支持请求的content-type,如果支持则直接读取数据,然后返回,后面的HttpMessageConverter不再执行,如下:
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 (logger.isDebugEnabled()) {
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
}
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;
}
}
响应转换过程:writeWithMessageConverters
首先,根据请求的accept和@RequestMapping的属性produces指定的返回类型确定最具体的返回类型,如下:
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
其次,依次遍历HttpMessageConverter,判断是否支持最终确定的MediaType,如果支持,则直接进行转换,然后返回;否则继续遍历。
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
outputValue = getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
}
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + converter + "]");
}
}
return;
}
}
SpringBoot 默认的HttpMessageConverters
详情可查看WebMvcConfigurationSupport的源码
添加额外的HttpMessageConverter
方式:向Spring容器注册HttpMessageConverters即可,比如添加FastJsonHttpMessageConverter;
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter fastConverter= new FastJsonHttpMessageConverter();
// fastConverter.setFastJsonConfig(getFastJsonConfig());
HttpMessageConverter<?> converter = fastConverter;
return new HttpMessageConverters(converter);
}
使用注意:FastJsonHttpMessageConverter默认支持的MediaType为*/*,因此如果其被被添加到HttpMessageConverters列表的第一个,则会处理所有的MediaType,此时最好缩小其支持的MediaType范围;
附录
当你有如下Accept头,将遵守如下规则进行应用:
①Accept:text/html,application/xml,application/json
将按照如下顺序进行produces的匹配 ①text/html ②application/xml ③application/json
②Accept:application/xml;q=0.5,application/json;q=0.9,text/html
将按照如下顺序进行produces的匹配 ①text/html ②application/json ③application/xml
参数为媒体类型的质量因子,越大则优先权越高(从0到1)
③Accept:/,text/,text/html
将按照如下顺序进行produces的匹配 ①text/html ②text/ ③*/*
参考