为什么说HttpMessageConverter的顺序非常重要_SpringBoot
问题描述
系统内配置了,ProtobufJsonFormatHttpMessageConverter和FastJsonHttpMessageConverter。
Spring官方内置的默认MessageConverter 比较标准,遇到什么 MediaType 就怎么解析。但是这两个比较特殊。
对于Protobuf生成的参数:
@PostMapping("/proto")
public ResponseEntity<String> proto(@RequestBody AddressBookProtos.Person person) {
try {
log.info("input is {}", JsonFormat.printer().print(person));
} catch (Exception e) {
//
}
return ResponseEntity.ok().body("ok");
}
这里用到的是普通的JSON请求,也就是Request Header 的 ContentType是 application/json;charset=UTF-8;
如果ProtobufJsonFormatHttpMessageConverter在FastJsonHttpMessageConverter 之后,那么读到的Protobuf消息是空白。
也就说:Controller的 RequestBody 参数是空白的字符串。
问题分析
先看 org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver
类:
//method: readWithMessageConverters()
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 (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;
}
}
这个类说明,Spring会根据Convert列表,逐个调用converter.canRead
,判断是否能够支持这种内容的读写。
FastJsonHttpMessageConverter 的canRead相当于直接返回true,因为mediaType 也支持 application/json;charset=UTF-8;
。
这里考虑到JSON只是一个字符串,所以没法根据类型判断能不能读。字符串肯定能读。所以FastJSON这个地方还不能直接说他这么设计不合理。
//FastJsonHttpMessageConverter.java
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
return super.canRead(contextClass, mediaType);
}
所以如果先找到了FastJsonHttpMessageConverter,那么FastJSON不认识 protobuf的 Bean,无法进行读写,因此读到一个空字符串。
再看看ProtobufJsonFormatHttpMessageConverter的实现:
//org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter#supports
@Override
protected boolean supports(Class<?> clazz) {
return Message.class.isAssignableFrom(clazz);
}
这里十分精确,他就是要支持Message接口的,所有的Protobuf定义message的时候,都会继承这个接口。
因此这里需要将 ProtobufJsonFormatHttpMessageConverter 提到FastJson之前。
解决方案
方案一
@Bean
public ProtobufJsonFormatHttpMessageConverter protobufJsonFormatHttpMessageConverter() {
return new ProtobufJsonFormatHttpMessageConverter();
}
这里定义的MessageConverter 会很早就扫描到Spring Context中。这里还不清楚为什么这个地方的ProtobufJsonFormatHttpMessageConverter 每次都是第一个。
尝试修改Configuration的类名字为z开头 也总是第一个。
同时FastJson转换器通常配置方式如下:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
fastJsonHttpMessageConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8));
converters.add(fastJsonHttpMessageConverter);
}
}
这样这个WebMvcConfigurer 在Spring Boot启动比较晚的时候才会加载,所以这里的MessageConverter 会排到最后面。
方案二(推荐)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ProtobufJsonFormatHttpMessageConverter protobufJsonFormatHttpMessageConverter = new ProtobufJsonFormatHttpMessageConverter();
converters.add(protobufJsonFormatHttpMessageConverter);
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
fastJsonHttpMessageConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8));
converters.add(fastJsonHttpMessageConverter);
}
}
这里configureMessageConverters 的调用顺序一定是在extendMessageConverters之前的。
参见:
//org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getMessageConverters
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
Spring并没有对HttpMessageConverter做什么特殊的排序。(只针对XML的排到最后,"with some slight re-ordering to put XML converters at the back of the list")
另外参考一篇cnBlog文章 讲的HttpMessageConverter的比较详细。
原文地址:https://www.cnblogs.com/slankka/p/11437034.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 关于useState的一切
- 关于useEffect的一切
- (25)Bash数值运算与运算符
- (27)正则表达式
- (28)字符截取命令cut、printf
- (29)字符截取命令awk
- RTSP协议视频平台EasyNVR流媒体服务器音频播放完毕后,视频为什么也会卡住?
- Redis | Redis 有序集合相关命令
- TypeScript 4.0正式发布!现在是开始使用它的最佳时机
- 微服务开源框架TARS 之 基础组件
- Gitlab-ci:从零开始的前端自动化部署
- 从 1 到 0 构建博客项目(导读)
- 应该在JavaScript中使用Class吗
- Go语言小书 | 关于编译和语法
- Go语言小书 | 小试牛刀,从hello world开始