HttpMessageConverter 实战总结
作者:互联网
阐述问题
最近发现一个关于FastJsonHttpMessageConverter特别有趣的一个点,它默认的supportMediaType竟然是MediaType.ALL。
/**
* Can serialize/deserialize all types.
*/
public FastJsonHttpMessageConverter() {
super(MediaType.ALL);
}
说明FastJsonHttpMessageConverter不配置supportMediaType,那么默认是MediaType.ALL。
FastJsonHttpMessageConverter应该显式配置其支持的消息格式是MediaType.APPLICATION_JSON_UTF8
//使用阿里 FastJson 作为JSON MessageConverter
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
log.info("converters:" + converters.toString());
List<MediaType> supportMediaTypeList = new ArrayList<>();
// supportMediaTypeList.add(MediaType.TEXT_HTML);
supportMediaTypeList.add(MediaType.APPLICATION_JSON_UTF8);
// supportMediaTypeList.add(MediaType.IMAGE_GIF);
// supportMediaTypeList.add(MediaType.IMAGE_JPEG);
// supportMediaTypeList.add(MediaType.IMAGE_PNG);
FastJsonConfig config = new FastJsonConfig();
// config.setDateFormat("yyyy-MM-dd HH:mm:ss");
config.setSerializerFeatures(
SerializerFeature.WriteMapNullValue,//保留空的字段
SerializerFeature.WriteNullStringAsEmpty,//String null -> ""
SerializerFeature.WriteNullNumberAsZero,//Number null -> 0
SerializerFeature.WriteNullListAsEmpty,//List null-> []
SerializerFeature.WriteNullBooleanAsFalse);//Boolean null -> false
converter.setFastJsonConfig(config);
converter.setSupportedMediaTypes(supportMediaTypeList);
converter.setDefaultCharset(Charset.forName("UTF-8"));
converters.add(converter);
}
我有一个业务场景,需要缓存商品详情页,手动渲染Thymeleaf模板,在Controller中返回html片段。
配置如下:
@ResponseBody
public String list(MiaoshaUser miaoshaUser) throws IOException {
modelMap.addAttribute("user", miaoshaUser);
//取缓存
String htmlCached = redisService.get(GoodsKey.getGoodsList, "", String.class);
if (!StringUtils.isEmpty(htmlCached)) {
return htmlCached;
}
List<GoodsVo> goodsVoList = goodsService.listGoodsVo();
modelMap.addAttribute("goodsList", goodsVoList);
SpringWebContext springWebContext = new SpringWebContext(request, response, request.getServletContext(),
request.getLocale(), modelMap, applicationContext);
String html = thymeleafViewResolver.getTemplateEngine().process("goods_list", springWebContext);
if (!StringUtils.isEmpty(html)) {
redisService.set(GoodsKey.getGoodsList, "", html);
}
return html;
}
可是最终渲染的界面惨不忍睹,界面出现了大量的\n\t这样的字符
惨不忍睹.png
如果我不用FastJsonHttpMessageConverter呢,界面却显示很正常,这是为什么呢。
正常.png
虽然在Controller中可以通过手动打开输出流,设置ContentType,把Thymeleaf模板输出到Response的body,可以解决问题。
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setStatus(200);
try {
response.getWriter().write(html);
} catch (IOException ex) {
log.error(ex.getMessage());
}
但是本着阿Q精神,我Debug消息转换器的流程,发现问题出在FastJsonHttpMessageConverter没有配置MediaType.APPLICATION_JSON_UTF8
,
导致其默认的MediaType.ALL影响Thymeleaf模板最后的输出。
Debug之路
RequestResponseBodyProcessor
实现了HandlerMethodReturnValueHandler
、HandlerMethodAgumentResolver
接口,实现了boolean supportsParamter()
、void resolveArgument()
、boolean supportsReturnType()
、void handleReturnValue()
所以具备了处理参数、处理返回值的能力。官方解释.RequestResponseBodyProcessor
能够解析用@RequestBody
注解的参数和通过使用HttpMessageConverter
读取并写入请求体或响应来处理用@ResponseBody
注解的方法的返回值。
/**
* 处理返回值
*/
public interface HandlerMethodReturnValueHandler {
/**
* 方法返回值是否被处理程序支持
*/
boolean supportsReturnType(MethodParameter returnType);
/**
* 通过向模型添加属性并设置视图或设置视图来处理给定的返回值
* {@link ModelAndViewContainer#setRequestHandled}标志到{@code true},表明响应已被直接处理。
*/
void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
/**
* 解析请求中的参数值
*/
public interface HandlerMethodArgumentResolver {
/**
* 是否能支持解析{@linkplain MethodParameter参数}
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 将方法参数解析为来自给定请求的参数值。
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {}
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
implements HandlerMethodReturnValueHandler {}
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {}
handleReturnValue()获取inputMessage,outMessage对象,调用writeWithMessageConverters()方法,让消息转换器对消息进行下一步处理.
public interface HttpMessage {
/**
* Return the headers of this message.
* @return a corresponding HttpHeaders object (never {@code null})
*/
HttpHeaders getHeaders();
}
public interface HttpOutputMessage extends HttpMessage {
/**
* Return the body of the message as an output stream.
* @return the output stream body (never {@code null})
* @throws IOException in case of I/O Errors
*/
OutputStream getBody() throws IOException;
}
public interface HttpInputMessage extends HttpMessage {
/**
* Return the body of the message as an input stream.
* @return the input stream body (never {@code null})
* @throws IOException in case of I/O Errors
*/
InputStream getBody() throws IOException;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
在AbstarctMessageConverterProcessor
的writeWithMessageConverters()
中,
根据HandlerMethod中的ReturnValueMethodParameter对象获valueType(返回值类型),同时也能得到outputValue(返回值)、decalredType(返回值实际类型)
Object outputValue;
Class<?> valueType;
Type declaredType;
if (value instanceof CharSequence) {
outputValue = value.toString();
valueType = String.class;
declaredType = String.class;
}
else {
outputValue = value;
valueType = getReturnValueType(outputValue, returnType);
declaredType = getGenericType(returnType);
}
根据request对象中获取请求中Accept的属性值,得requestedMediaTypes
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
}
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
continue;
}
return mediaTypes;
}
return Collections.emptyList();
}
根据HandlerMapping中的produces属性获得producibleMediaTypes。
如果没有显式设置produces属性,我们只能通过遍历所有的HttpMessageConverter,通过canWrite()方法找到支持解析Java对象的HttpMessageConverter,并且把其所支持的mediaType加入mediaTypes集合里面。
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<MediaType>(mediaTypes);
}
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<MediaType>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && declaredType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
通过两次for循环,比较requestedMediaTypes和producibleMediaTypes
得到compatiableMediaTypes。如果compatiableMediaTypes为空,会抛出HttpMediaTypeNotAcceptableException异常。白话意思就是producibleMediaTypes没有一个MediaType与requestedMediaTypes匹配,肯定无法执行下一步了。
if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
}
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
排序、for循环compatiableMediaTypes,通过isConcrete()判断消息格式是否具体(类型和子类型是否为通配符*),得到selectedMediaType(最终的MediaType)
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
for循环已配置所有的HttpMessageConverter,调用canWrite()方法,根据valueType(返回值类型)和selectedMediaType来判断消息是否可以转换。
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
if (((GenericHttpMessageConverter) messageConverter).canWrite(
declaredType, valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
((GenericHttpMessageConverter) messageConverter).write(
outputValue, declaredType, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
return;
}
}
else if (messageConverter.canWrite(valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
return;
}
}
}
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
如果没有配置MediaType.APPLICATION_JSON_UTF8
,默认值是MediaType.ALL
,FastJsonHttpMessageConverter
会去处理消息格式为"text/html;charset=UTF-8",会执行这一段代码
if (messageConverter instanceof GenericHttpMessageConverter) {
if (((GenericHttpMessageConverter) messageConverter).canWrite(
declaredType, valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
((GenericHttpMessageConverter) messageConverter).write(
outputValue, declaredType, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
return;
}
}
接着调用FastJsonHttpMessageConverter的write()方法写入消息
/*
* @see org.springframework.http.converter.GenericHttpMessageConverter.write
*/
public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
super.write(o, contentType, outputMessage);// support StreamingHttpOutputMessage in spring4.0+
//writeInternal(o, outputMessage);
}
接着调用AbstaractHttpMessageConverter
中的write()写入响应头和消息内容
/**
* This implementation sets the default headers by calling {@link #addDefaultHeaders},
* and then calls {@link #writeInternal}.
*/
@Override
public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage =
(StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(final OutputStream outputStream) throws IOException {
writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() throws IOException {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
});
}
});
}
else {
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
}
接着调用FastJsonHttpMessageConverter的writeInternal()进行消息转换
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
ByteArrayOutputStream outnew = new ByteArrayOutputStream();
try {
HttpHeaders headers = outputMessage.getHeaders();
//获取全局配置的filter
SerializeFilter[] globalFilters = fastJsonConfig.getSerializeFilters();
List<SerializeFilter> allFilters = new ArrayList<SerializeFilter>(Arrays.asList(globalFilters));
boolean isJsonp = false;
//不知道为什么会有这行代码, 但是为了保持和原来的行为一致,还是保留下来
Object value = strangeCodeForJackson(object);
if (value instanceof FastJsonContainer) {
FastJsonContainer fastJsonContainer = (FastJsonContainer) value;
PropertyPreFilters filters = fastJsonContainer.getFilters();
allFilters.addAll(filters.getFilters());
value = fastJsonContainer.getValue();
}
//revise 2017-10-23 ,
// 保持原有的MappingFastJsonValue对象的contentType不做修改 保持旧版兼容。
// 但是新的JSONPObject将返回标准的contentType:application/javascript ,不对是否有function进行判断
if (value instanceof MappingFastJsonValue) {
if(!StringUtils.isEmpty(((MappingFastJsonValue) value).getJsonpFunction())){
isJsonp = true;
}
} else if (value instanceof JSONPObject) {
isJsonp = true;
}
int len = JSON.writeJSONString(outnew, //
fastJsonConfig.getCharset(), //
value, //
fastJsonConfig.getSerializeConfig(), //
//fastJsonConfig.getSerializeFilters(), //
allFilters.toArray(new SerializeFilter[allFilters.size()]),
fastJsonConfig.getDateFormat(), //
JSON.DEFAULT_GENERATE_FEATURE, //
fastJsonConfig.getSerializerFeatures());
if (isJsonp) {
headers.setContentType(APPLICATION_JAVASCRIPT);
}
if (fastJsonConfig.isWriteContentLength()) {
headers.setContentLength(len);
}
outnew.writeTo(outputMessage.getBody());
} catch (JSONException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
} finally {
outnew.close();
}
}
我们可以观察到这个方法中有一个JSON.writeJSONString()方法,我点进去看一看
public static final int writeJSONString(OutputStream os, //
Charset charset, //
Object object, //
SerializeConfig config, //
SerializeFilter[] filters, //
String dateFormat, //
int defaultFeatures, //
SerializerFeature... features) throws IOException {
SerializeWriter writer = new SerializeWriter(null, defaultFeatures, features);
try {
JSONSerializer serializer = new JSONSerializer(writer, config);
if (dateFormat != null && dateFormat.length() != 0) {
serializer.setDateFormat(dateFormat);
serializer.config(SerializerFeature.WriteDateUseDateFormat, true);
}
if (filters != null) {
for (SerializeFilter filter : filters) {
serializer.addFilter(filter);
}
}
serializer.write(object);
int len = writer.writeToEx(os, charset);
return len;
} finally {
writer.close();
}
}
一看吓一跳。卧槽,把html序列化了,GG。终于找到问题的始作俑者了。
如果配置了MediaType.APPLICATION_JSON_UTF8,FastJsonHttpMessageConverter
只能处理"application/json;charset=UTF-8"的消息,"text/html;charset=UTF-8"格式的消息被StringHttpMessageConverter得到了处理,会执行这一段代码
else if (messageConverter.canWrite(valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
return;
}
接着调用AbstaractHttpMessageConverter
中的write()写入响应头和消息内容
@Override
public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage =
(StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(final OutputStream outputStream) throws IOException {
writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() throws IOException {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
});
}
});
}
else {
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
}
最终调用StringHttpMessageConverter
的writeInternal写入消息到outputMessage.getBody()中,输出html片段。
@Override
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
if (this.writeAcceptCharset) {
outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
}
Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
StreamUtils.copy(str, charset, outputMessage.getBody());
}
关于HttpMessageConverter
加载顺序,可以在WebMvcConfigurationSupport
看到端倪。
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());
}
}
我们其实是重写configureMessageConverters()方法去配置FastJsonHttpMessageConverter,所以它是第一个。
K.B.Zhu 发布了7 篇原创文章 · 获赞 4 · 访问量 9807 私信 关注
标签:实战,总结,HttpMessageConverter,outputMessage,MediaType,selectedMediaType,new,null,ou 来源: https://blog.csdn.net/qwesxd/article/details/104193622