让SpringBoot的jackson支持JavaBean嵌套的protobuf
问题背景
REST 项目使用protobuf 来加速项目开发,定义了很多model,vo,最终返回的仍然是JSON.
项目中一般使用 一个Response类,
public class Response<T> {
int code;
String message;
T data;
}
如果需要分页,则还需要如下的类
public class Pagedata<T> {
long totalcount;
List<T> datas;
}
那么在Controller中,直接返回
Response
.set( Pagedata. set ( Protobuf类 ) )
这种形式,会被Spring的HttpMessageConverter 识别为 Response类,而不是protobuf类,因此选择了正常的 jackson MessageConverter。
这个时候,会报错:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain: com.xxx.crm.proto.xxxx["unknownFields"]-
由此可见 jackson 不支持Protobuf类的JSON序列化。
解决方案
思路一
如果希望被HttpMessageConverter 正确选择 ProtobufJsonFormatHttpMessageConverter,那么整个类都应该是Protobuf的类。那么要使用
如下的写法:
import "google/protobuf/any.proto";
option java_outer_classname = "ResponseProto";
message ProtoResponse {
int32 code = 1;
string message = 2;
ProtoPagedData data = 3;
}
message ProtoPagedData {
repeated google.protobuf.Any datas = 1;
int64 totalcount = 2;
}
不管什么类都需要用此Protobuf类来 pack。
@GetMapping(value = "/someUrl")
public Object handler() {
List<FooBarProtobufVO> data = //
ResponseProto.ProtoResponse ok = ResponseProto.ProtoResponse.newBuilder()
.setCode(0)
.setMsg("ok")
.setData(ResponseProto.ProtoPagedData.newBuilder()
.addAllDatas(data.stream().map(Any::pack).collect(Collectors.toList()))
.setTotalcount(all.getTotalElements())
.build())
.build();
return ok;
}
注意:如果使用Any需要使用TypeRegistry显式注册你的实际类型,否则使用JsonFormat.printer().print打印的时候,会报错:Cannot find type for url: type.googleapis.com
这个方式最终是通过ProtobufJsonFormatHttpMessageConverter序列化的。
(我的另一篇文章也指出了,HttpMessageConverter的顺序十分重要,这里需要让ProtobufJsonFormatHttpMessageConverter 在系统的靠前的位置)
思路二
既然protobuf的类不能被jackson正确序列化,那么直接返回一个String,或许使用 JsonFormat也是一个不错的选择。
JsonFormat.printer()
.omittingInsignificantWhitespace()
.preservingProtoFieldNames()
.includingDefaultValueFields()
.print(messageOrBuilder);
通过 JsonFormat打印出protobuf JSON形式,但是这个的缺陷是 JsonFormat不支持 list 的 Protobuf类,仅支持单个的protobuf类。
那么只能按照思路一的方式把他套进一个repeated 的 proto中。
得到JSON之后,如果又希望能灵活的往数据结构中增加字段,例如 code/msg/data/ 这种形式,不满足,还需要增加某些临时的字段例如 successCount, totalCount, errorCount 等等
这个时候,还需要用FASTJSON 再将这个字符串使用JSON.parseObject 得到 一个 JSONObject,再添加一些字段。这样比较麻烦,但是也能解决问题。
这种情况返回给HttpMessageConverter处理的是String,因此最终会被StringHttpMessageConverter序列化。
(为了严谨,这里因为是StringHttpMessageConverter处理,那么ResponseHeader 的Content-Type是 text/plain;charset=UTF-8,严格来讲,如果客户端没有正确识别这个JSON字符串,因此还需要在Controller的方法上面,增加额外的produces = MediaType.APPLICATION_JSON_UTF8_VALUE )
思路三
jackson那么强大,直接让jackson支持protobuf行不行?
答案是行。
找到jackson的 github项目页面
然后 发现,readme下方有
jackson-datatype-protobuf for handling datatypes defined by the standard Java protobuf library, developed by HubSpot
NOTE! This is different from jackson-dataformat-protobuf which adds support for encoding/decoding protobuf content but which does NOT depend on standard Java protobuf library
点进入查看 jackson-datatype-protobuf
Jackson module that adds support for serializing and deserializing Google's Protocol Buffers to and from JSON.
Usage
Maven dependency
To use module on Maven-based projects, use following dependency:
<dependency>
<groupId>com.hubspot.jackson</groupId>
<artifactId>jackson-datatype-protobuf</artifactId>
<version><!-- see table below --></version>
</dependency>
那么怎么集成到SpringBoot中呢?
- 引入上述第三方jackson-datatype-protobuf的依赖
- 在项目中引入ProtobufModule。
@Configuration
public class JacksonProtobufSupport {
@Bean
@SuppressWarnings("unchecked")
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return jacksonObjectMapperBuilder -> {
jacksonObjectMapperBuilder.featuresToDisable(
JsonGenerator.Feature.IGNORE_UNKNOWN,
MapperFeature.DEFAULT_VIEW_INCLUSION,
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
);
jacksonObjectMapperBuilder.propertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);//如果字段都是驼峰命名规则,需要这一句
jacksonObjectMapperBuilder.modulesToInstall(ProtobufModule.class);
};
}
}
完美解决
让SpringBoot的jackson支持JavaBean嵌套的protobuf的更多相关文章
- 让jquery easyui datagrid列支持绑定嵌套对象
嵌套对象是指返回的json数据,是对象的某个属性自带有属性.而我们恰恰又需要这个属性,默认情况下easyui的datagrid是不支持绑定嵌套对象的.比如:datagrid的field属性只能为fie ...
- springboot之jackson的两种配置方式
springboot 针对jackson是自动化配置的,如果需要修改,有两种方式: 方式一:通过application.yml 配置属性说明:## spring.jackson.date-format ...
- springboot 配置jsp支持
springboot默认并不支持jsp模板,所以需要配置. 下面是一个可以运行的例子: 首先配置属性文件: spring.http.encoding.force=true spring.http. ...
- Springboot中IDE支持两种打包方式,即jar包和war包
Springboot中IDE支持两种打包方式,即jar包和war包 打包之前修改pom.xml中的packaging节点,改为jar或者war 在项目的根目录执行maven 命令clean pa ...
- SpringBoot 多数据库支持:
SpringBoot 多数据库支持: springboot2.0+mybatis多数据源集成 https://www.cnblogs.com/cdblogs/p/9275883.html Spring ...
- SpringBoot与jackson.databind兼容报错问题
SpringBoot与jackson.databind兼容报错问题 ———————————————— 1.SpringBoot版本V2.0.0其依赖的jackson-databind版本为V2.9.4 ...
- Springboot(二)springboot之jsp支持
参考恒宇少年的博客:https://www.jianshu.com/p/90a84c814d0c springboot内部对jsp的支持并不是特别理想,而springboot推荐的视图是Thymele ...
- 源码分析springboot自定义jackson序列化,默认null值个性化处理返回值
最近项目要实现一种需求,对于后端返回给前端的json格式的一种规范,不允许缺少字段和字段值都为null,所以琢磨了一下如何进行将springboot的Jackson序列化自定义一下,先看看如何实现,再 ...
- SpringBoot系列——Jackson序列化
前言 Spring Boot提供了与三个JSON映射库的集成: Gson Jackson JSON-B Jackson是首选的默认库. 官网介绍: https://docs.spring.io/spr ...
随机推荐
- Java秒杀系统优化的工程要点
这篇博客是笔者学习慕课网若鱼老师的<Java秒杀系统方案优化 高性能高并发实战>课程的学习笔记.若鱼老师授课循循善诱,讲解由浅入深,欢迎大家支持. 本文记录课程中的注意点,方便以后code ...
- 抓住那只牛!Catch That Cow POJ-3278 BFS
题目链接:Catch That Cow 题目大意 FJ丢了一头牛,FJ在数轴上位置为n的点,牛在数轴上位置为k的点.FJ一分钟能进行以下三种操作:前进一个单位,后退一个单位,或者传送到坐标为当前位置两 ...
- C#连接Mongo报Unable to authenticate using sasl protocol mechanism SCRAM-SHA-1错的解决方案
---恢复内容开始--- 最近做一个基于ABP的.net Core的项目,数据库选了MongoDB,但是返现无法给数据库设置认证,只要设置了账号密码连接就报错 连接串如下: mongodb://roo ...
- Spring 梳理-bean作用域
Spring定义了多种域 单例(Singleton):在整个应用中,只有一个实例 原型(Prototype):每次注入或者通过Spring应用上线文获取时,都创建一个bean实例 会话(Session ...
- 转 Oracle中关于处理小数点位数的几个函数,取小数位数,Oracle查询函数
关于处理小数点位数的几个oracle函数() 1. 取四舍五入的几位小数 select round(1.2345, 3) from dual; 结果:1.235 2. 保留两位小数,只舍 select ...
- office2019激活
这个是在网上偶然看见的一个激活方式,分享一下. 复制如下代码保存后修改文件后缀名为".bat",请注意有一个点,然后保存以管理员身份运行即可: @echo off(cd /d &q ...
- 集合线性表--List之ArrayList
集合操作——线性表 List: add().remove().subList().list.toArray().array.asList(). List排序: Collections.sort(li ...
- [Note] Windows 10 Python 3.6.4 安装scrapy
直接使用pip install安装时会在安装Twisted出错,以下主要是解决Twisted的安装问题 1. 安装wheel pip install wheel 2. 安装Twisted 在Pytho ...
- Executor线程池原理详解
线程池 线程池的目的就是减少多线程创建的开销,减少资源的消耗,让系统更加的稳定.在web开发中,服务器会为了一个请求分配一个线程来处理,如果每次请求都创建一个线程,请求结束就销毁这个线程.那么在高并发 ...
- 超详细!! sql server 同步数据库 发布 订阅 跨网段 无公网ip 常见问题
问题描述 主机1:发布端 阿里云服务器--有公网ip 主机2:订阅端 笔记本--无公网ip 数据量很小,主要是熟悉发布订阅的操作流程. 主机2仅仅作为主机1的本地备份,要求修改云服务器上数据后,能通过 ...