为什么说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的比较详细。
为什么说HttpMessageConverter的顺序非常重要_SpringBoot的更多相关文章
- 通过FeignClient接收shaded的javabean的JSON序列化
问题说明 最近做了关于flink的需求. 现在需要通过HTTP访问FLINK的 RESTAPI, rest 接口的JSON 非常庞大而复杂. 那么怎么去完整的接收数据呢? 方法一就是手写部分需要的Ja ...
- Java提高篇——静态代码块、构造代码块、构造函数以及Java类初始化顺序
静态代码块:用staitc声明,jvm加载类时执行,仅执行一次构造代码块:类中直接用{}定义,每一次创建对象时执行.执行顺序优先级:静态块,main(),构造块,构造方法. 构造函数 public H ...
- Openwrt笔记-IPv6与启动顺序
之前使用了nat6方案和x3c8021x实现了校园网上网和IPv6连接:但实际使用时经常出现莫名奇妙的问题.IPv6状态要么是无法连接网络,要么是无法连接Internet:经过研究,发现大概是自启动项 ...
- [Django高级]理解django中的中间件机制和执行顺序
原文来自 Understanding Django Middlewares, 这篇文章从整体上介绍了django中中间件定义,作用,和怎么样自己写中间件 –orangleliu. 注:middlewa ...
- django中的中间件机制和执行顺序
这片文章将讨论下面内容: 1.什么是middleware 2.什么时候使用middleware 3.我们写middleware必须要记住的东西 4.写一些middlewares来理解中间件的工作过程和 ...
- shell命令技巧——文本去重并保持原有顺序
简单来说,这个技巧相应的是例如以下一种场景 假设有文本例如以下 cccc aaaa bbbb dddd bbbb cccc aaaa 如今须要对它进行去重处理.这个非常easy,sort -u就能够搞 ...
- Java基础系列5:Java代码的执行顺序
该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 一.构造方法 构造方 ...
- java类中元素初始化顺序
结论:对于静态变量.静态初始化块.变量.初始化块.构造器,它们的初始化顺序依次是(静态变量.静态初始化块)>(变量.初始化块)>构造器. public class Test4 { @Tes ...
- Java——Java代码的执行顺序
该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 一.构造方法 构造方 ...
随机推荐
- 生物医学命名实体识别(BioNER)研究进展
生物医学命名实体识别(BioNER)研究进展 最近把之前整理的一些生物医学命名实体识别(Biomedical Named Entity Recognition, BioNER)相关的论文做了一个Bio ...
- 基于docker构建测试环境
目录 0x01介绍 0x02 镜像基本操作 0x03 容器基本操作 0x04 容器的修改与保存 0x05 使用Dockerfile定制镜像 0x01介绍 Docker 是一个开源的应用容器引擎,基于 ...
- 【面试题】Java常见面试题
集合与数组? 数组:(可以存储基本数据类型)是用来存储对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用 集合:(只能存储对象,对象类型可以不一样)集合的长度可变,可在多数情况下使用 ...
- ELK7.3实战安装配置文档
整体架构 一:环境准备 1.环境规划准备 192.168.43.16 jdk,elasticsearch-master ,logstash,kibana 192.168.43.17 jdk,ela ...
- C语言I—2019秋作业02
1.[新增内容] 这个作业属于那个课程 C语言程序设计 这个作业要求在哪里 <C语言I-2019秋作业02> 我在这个课程的目标是 这个作业在那个具体方面帮助我实现目标 <实现一些基 ...
- java基础之缓存:session、cookie和cache的区别
以前实现数据的缓存有很多种方法,有客户端的Cookie,有服务器端的Session和Application. 其中Cookie是保存在客户端的一组数据,主要用来保存用户名等个人信息. Session则 ...
- Quartz系列(一):基础介绍
新建一个.NET Core控制台项目,NuGet引用Quartz引用. class Program { static void Main(string[] args) { var task = Tas ...
- RabbitMQ原理介绍
RabbitMQ历史 RabbitMQ消息系统是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.在同步消息通讯的世界里有很多公开标准(如COBAR的IIO ...
- php7和php5区别是什么
PHP7距正式发布以及有挺长时间了,刚出道就号称比旧版本快了几倍,各种开源框架或系统运行在PHP7上速度效率提高了几倍.那么php7和php5之间的区别是什么?下面本篇文章就来给大家简单介绍一下,希望 ...
- (八十四)c#Winform自定义控件-导航菜单(类Office菜单)
前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kwwwvagaa/NetWinformControl 码云:ht ...