版本springboot-2.2.6
- HttpMessageConverter在SpringMVC中的作用
- 最近通过configureMessageConverters重写HttpMessageConverter时候,通过浏览器请求出现了乱码,响应头也从Content-Type :application/json 变成Content-Type: text/html,下面是代码片段
// 字符串
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
stringHttpMessageConverter.setWriteAcceptCharset(false);
converters.add(stringHttpMessageConverter);
// 默认JSON
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = mappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(new LinkedList<MediaType>(){{
add(MediaType.APPLICATION_JSON);
}});
converters.add(new StringHttpMessageConverter());
ObjectMapper objectMapper = new ObjectMapper();
ObjectMapperUtil.setObjectMapper(objectMapper);
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(0, mappingJackson2HttpMessageConverter);
//fastJson
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
converters.add(fastJsonHttpMessageConverter);
-
通过分析找到源码,消息出口的convert方法writeWithMessageConverters,发现spring的响应式根据请求的accept来匹配相应的content-type类型的,下面是我本次请求的accept
-
接下来奇怪的事情发生了
-
我明明只配置了一个content-type=application/json类型,发现却有一个全匹配类型content-type=/,
-
导致所有的accept类型都被添加到了List mediaTypesToUse = new ArrayList(); 这个可使用的数组里,在迭代mediaTypesToUse时,随即取出了其中的第一个类型text/html,并且这个text/html,是没有charset编码的,所以导致了乱码
-
那么为什么会出现*/*嘛,继续往下走
-
这段代码我们可以看出,有三个自定义的消息处理器,分别支持application/json,text/plain,/,由于Stirng的消息处理器,相应类型就被排除了,代码如下
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
Set<MediaType> mediaTypes = (Set)request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList(mediaTypes);
} else if (this.allSupportedMediaTypes.isEmpty()) {
return Collections.singletonList(MediaType.ALL);
} else {
List<MediaType> result = new ArrayList();
Iterator var6 = this.messageConverters.iterator();
while(true) {
while(var6.hasNext()) {
HttpMessageConverter<?> converter = (HttpMessageConverter)var6.next();
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter)converter).canWrite(targetType, valueClass, (MediaType)null)) {
result.addAll(converter.getSupportedMediaTypes());
}
} else if (converter.canWrite(valueClass, (MediaType)null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
}
}
-
这段是获取支持的media-type类型,如果Converter能够write就算支持,很显然text/plain不能写json类型,默认支持所有类型
-
因此只有FastJsonHttpMessageConverter这个转换器,由于没有配置任何的media-type,默认设置MediaType.ALL
public FastJsonHttpMessageConverter() {
super(MediaType.ALL);
}
- 在与accept匹配时,如果converter支持所有类型,那么优先权在于Accept,否则尽可能匹配并使用converter支持类型
解决方案
- 1、给fastJsonHttpMessageConverter设置支持类型,
fastJsonHttpMessageConverter.setSupportedMediaTypes(new LinkedList<MediaType>(){{ add(MediaType.APPLICATION_JSON); }});
- 但这样违背了初衷,这样就不能解析text类型转json,fastJson可以接收text的json类型,可以默认下划线转驼峰等,比如我下面这个请求
- 在AbstractMessageConverterMethodArgumentResolver消息解析时,便使用到了fastJsonHttpMessageConverter
结论
- 可以通过配置 add(MediaType.valueOf(“text/html;charset=UTF-8”)),使其满足accept的匹配要求,accept便会使用MediaType
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(new LinkedList<MediaType>(){{
add(MediaType.APPLICATION_JSON);
add(MediaType.valueOf("text/html;charset=UTF-8"));
}});