问题描述

系统内配置了,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的更多相关文章

  1. 通过FeignClient接收shaded的javabean的JSON序列化

    问题说明 最近做了关于flink的需求. 现在需要通过HTTP访问FLINK的 RESTAPI, rest 接口的JSON 非常庞大而复杂. 那么怎么去完整的接收数据呢? 方法一就是手写部分需要的Ja ...

  2. Java提高篇——静态代码块、构造代码块、构造函数以及Java类初始化顺序

    静态代码块:用staitc声明,jvm加载类时执行,仅执行一次构造代码块:类中直接用{}定义,每一次创建对象时执行.执行顺序优先级:静态块,main(),构造块,构造方法. 构造函数 public H ...

  3. Openwrt笔记-IPv6与启动顺序

    之前使用了nat6方案和x3c8021x实现了校园网上网和IPv6连接:但实际使用时经常出现莫名奇妙的问题.IPv6状态要么是无法连接网络,要么是无法连接Internet:经过研究,发现大概是自启动项 ...

  4. [Django高级]理解django中的中间件机制和执行顺序

    原文来自 Understanding Django Middlewares, 这篇文章从整体上介绍了django中中间件定义,作用,和怎么样自己写中间件 –orangleliu. 注:middlewa ...

  5. django中的中间件机制和执行顺序

    这片文章将讨论下面内容: 1.什么是middleware 2.什么时候使用middleware 3.我们写middleware必须要记住的东西 4.写一些middlewares来理解中间件的工作过程和 ...

  6. shell命令技巧——文本去重并保持原有顺序

    简单来说,这个技巧相应的是例如以下一种场景 假设有文本例如以下 cccc aaaa bbbb dddd bbbb cccc aaaa 如今须要对它进行去重处理.这个非常easy,sort -u就能够搞 ...

  7. Java基础系列5:Java代码的执行顺序

    该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 一.构造方法 构造方 ...

  8. java类中元素初始化顺序

    结论:对于静态变量.静态初始化块.变量.初始化块.构造器,它们的初始化顺序依次是(静态变量.静态初始化块)>(变量.初始化块)>构造器. public class Test4 { @Tes ...

  9. Java——Java代码的执行顺序

    该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 一.构造方法 构造方 ...

随机推荐

  1. elastic操作-索引重命名,索引副本数修改

    目前我们使用的elastic版本为2.3.5 当前版本没有直接的curl操作可以更改索引的名称,索引的副本数. 有直接更改索引副本数的api. curl -XPUT "192.168.1.1 ...

  2. 前端面试题——html与css基础篇

    整理一波html和css的面试题,侧重基础,希望明天面试能用到~(╥╯^╰╥) 一.HTML部分 1.浏览器页面有哪三层构成,分别是什么,作用是什么? 构成:结构层.表示层.行为层分别是:HTML.C ...

  3. JAVA设计模式---单例模式篇

    单例模式(singleton):是JAVA中最简单的一种设计模式,属于创建型模式.所谓单例,就是整个程序有且仅有一个实例. 特点: 构造方法私有化 在本类中实例化一个对象作为本类的属性 对外提供一个访 ...

  4. Scrapy项目 - 实现豆瓣 Top250 电影信息爬取的爬虫设计

    通过使Scrapy框架,掌握如何使用Twisted异步网络框架来处理网络通讯的问题,进行数据挖掘和对web站点页面提取结构化数据,可以加快我们的下载速度,也可深入接触各种中间件接口,灵活的完成各种需求 ...

  5. 数据分析--pandas的基本使用

    一.pandas概述 1.pandas是一个强大的Python数据分析的工具包,是基于NumPy构建的. 2.pandas的主要功能 具备对其功能的数据结构DataFrame.Series 集成时间序 ...

  6. javascript:history.go(-1)的使用

    1.问题描述 在微信项目开发中,比如常用联系人的增删改查操作中,比如跳入常用联系人管理页面,选中一个联系人修改它,就会跳入修改页面,修改完成后跳转到常用联系人管理页面,此时如果修改成功跳转采用的是页面 ...

  7. 死磕 java同步系列之mysql分布式锁

    问题 (1)什么是分布式锁? (2)为什么需要分布式锁? (3)mysql如何实现分布式锁? (4)mysql分布式锁的优点和缺点? 简介 随着并发量的不断增加,单机的服务迟早要向多节点或者微服务进化 ...

  8. Marshmallow权限使用

    Google发布Android 6.0后对用权限的控制更加严格,在Android5.1或更低的版本中用户能在App的安装期间或使用设置应用程序权限来同意或拒绝某个权限,而在Android6.0或更高的 ...

  9. eclipse快捷键——纪念我突然失忆的脑子

    常用快捷键 1.快速new出来一个对象    Alt+/ 2.快速格式化代码              Ctrl+I 3.快速导入包                     Ctrl+Shift+O

  10. 使用gtest(googletest)进行c++单元测试

    这是系列文章的第三篇,前两篇https://www.cnblogs.com/gaopang/p/11243367.html和https://www.cnblogs.com/gaopang/p/1158 ...