OR博客
让SpringBoot的jackson支持JavaBean嵌套的protobuf
OrdinaryRoad
创建于:2024-01-08 18:31:22
河南省
0
15
164
0
> 转载自:[让SpringBoot的jackson支持JavaBean嵌套的protobuf - 一杯半盏 - 博客园 (cnblogs.com)](https://www.cnblogs.com/slankka/p/11653114.html) # 问题背景 REST 项目使用protobuf 来加速项目开发,定义了很多model,vo,最终返回的仍然是JSON. 项目中一般使用 一个Response类, ```csharp public class Response<T> { int code; String message; T data; } ``` 如果需要分页,则还需要如下的类 ```csharp public class Pagedata<T> { long totalcount; List<T> datas; } ``` 那么在Controller中,直接返回 Response .set( Pagedata. set ( Protobuf类 ) ) 这种形式,会被Spring的HttpMessageConverter 识别为 Response类,而不是protobuf类,因此选择了正常的 jackson MessageConverter。 这个时候,会报错: ```mipsasm 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的类。那么要使用 如下的写法: ```java 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。 ```haskell @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也是一个不错的选择。 ```scss 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[项目页面](https://github.com/FasterXML/jackson) 然后 发现,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](https://github.com/HubSpot/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: ```xml <dependency> <groupId>com.hubspot.jackson</groupId> <artifactId>jackson-datatype-protobuf</artifactId> <version><!-- see table below --></version> </dependency> ``` 那么怎么集成到SpringBoot中呢? 1. 引入上述第三方jackson-datatype-protobuf的依赖 2. 在项目中引入ProtobufModule。 ```java @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); }; } } ``` ***完美解决***
评论