REST 項目使用protobuf 來加速項目開發,定義了不少model,vo,最終返回的仍然是JSON.java
項目中通常使用 一個Response類,git
public class Response<T> { int code; String message; T data; }
若是須要分頁,則還須要以下的類github
public class Pagedata<T> { long totalcount; List<T> datas; }
那麼在Controller中,直接返回api
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序列化。app
若是但願被HttpMessageConverter 正確選擇 ProtobufJsonFormatHttpMessageConverter,那麼整個類都應該是Protobuf的類。那麼要使用
以下的寫法:ui
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。google
@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.comurl
這個方式最終是經過ProtobufJsonFormatHttpMessageConverter序列化的。spa
(個人另外一篇文章也指出了,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中呢?
@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); }; } }
完美解決