果FastJsonHttpMessageConverter不配置supportMediaType,那么默认是MediaType.ALL。
FastJsonHttpMessageConverter应配置MediaType.APPLICATION_JSON_UTF8
List supportMediaTypeList = new ArrayList<>();
supportMediaTypeList.add(MediaType.APPLICATION_JSON_UTF8);
converter.setSupportedMediaTypes(supportMediaTypeList);
我有一个业务场景,需要缓存商品详情页,手动渲染Thymeleaft模板,在Controller中返回html片段。
配置如下:
@RequestMapping(value = "/to_list", produces = "text/html;charset=UTF-8")
@ResponseBody
public String list(MiaoshaUser miaoshaUser){
...
return html;
}
虽然在Controller中可以通过手动打开输出流,设置ContentType,把Thymeleaf模板输出到Response的body,可以解决问题。
但是本着阿Q精神,我Debug消息转换器的流程,发现问题出在FastJsonHttpMessageConverter没有配置MediaType.APPLICATION_JSON_UTF8,
导致其默认的MediaType.ALL影响Thymeleaf模板最后的输出。
1.RequestResponseBodyProcessor实现了HandlerMethodReturnValueHandler、HandlerMethodAgumentResolver接口,
实现了boolean supportsParamter()、void resolveArgument()、boolean supportsReturnType()、void handleReturnValue()
所以具备了处理参数、处理返回值的能力。
2.handleReturnValue()获取inputMessage,outMessage对象,
调用writeWithMessageConverters()方法,让消息转换器对消息进行下一步处理.
3.在AbstarctMessageConverterProcess的writeWithMessageConverters()中,
根据HandlerMethod中的ReturnValueMethodParameter对象获取valueType(返回值类型),同时也能得到
outputValue(返回值)、decalredType(返回值实际类型)
4.根据request对象中获取请求中Accept的属性值,得到requestedMediaTypes,
根据HandlerMapping中的produces属性获得producibleMediaTypes。如果没有设置produces属性,
默认遍历系统中所有的HttpMessageConverter,找到合适的supportMediaTypes。
通过两次for循环,比较requestedMediaTypes和producibleMediaTypes
得到compatiableMediaTypes。如果compatiableMediaTypes为空,会抛出HttpMediaTypeNotAcceptableException异常
白话意思就是producibleMediaTypes没有一个MediaType与requestedMediaTypes匹配,肯定无法执行下一步了。
5.排序、for循环compatiableMediaTypes,得到selectedMediaType(最终的MediaType)
6.for循环已配置所有的HttpMessageConverter,调用canWrite()方法,根据valueType(返回值类型)
和selectedMediaType来判断消息是否可以转换。
如果没有配置MediaType.APPLICATION_JSON_UTF8,默认值是MediaType.ALL,FastJsonHttpMessageConverter
会去处理消息格式为"text/html;charset=UTF-8",
调用方法如下:
=》write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) :AbstractHttpMessageConverter
=》writeInternal(t, outputMessage) :FastJsonHttpMessageConverter
=》JSON.writeJSONString(...), serializer.write(object)
卧槽,把html序列化了,GG,界面出现大量的\n\t
如果配置了MediaType.APPLICATION_JSON_UTF8,FastJsonHttpMessageConverter
只能处理"application/json;charset=UTF-8"的消息,"text/html;charset=UTF-8"格式的消息被StringHttpMessageConverter得到了处理,
调用方法如下:
=》write(t,contentType, outputMessage) :AbstractHttpMessageConverter
=> writeInternal(t, outputMessage) :StringHttpMessageConverter
=>StreamUtils.copy(str, charset, outputMessage.getBody()) :StringHttpMessageConverter
输出html片段
最后HttpMessageConverter加载顺序,可以在WebMvcConfigurationSupport看到端倪。
我们其实是重写configureMessageConverters()方法去配置FastJsonHttpMessageConverter,
所以它是第一个。
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(
Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build()));
}
else if (jaxb2Present) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
messageConverters.add(new MappingJackson2HttpMessageConverter(
Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build()));
}
else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
}
}
根据上面的源码可知先配置FastJsonHttpMessageConverter,其他的走默认。顺序很重要
@Override protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) { FastJsonHttpMessageConverter fastConvertor = new FastJsonHttpMessageConverter(); FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteNullListAsEmpty); fastConvertor.setFastJsonConfig(fastJsonConfig); List<MediaType> supportedMediaTypes = new ArrayList<MediaType>(); supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); supportedMediaTypes.add(MediaType.APPLICATION_JSON); fastConvertor.setSupportedMediaTypes(supportedMediaTypes); converters.add(fastConvertor); super.addDefaultHttpMessageConverters(converters); }