问题背景

REST 项目使用protobuf 来加速项目开发,定义了很多model,vo,最终返回的仍然是JSON.

项目中一般使用 一个Response类,

public class Response<T> {
  int code;
  String message;
  T data;
}

如果需要分页,则还需要如下的类

public class Pagedata<T> {
  long totolcount;
  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的类。那么要使用
如下的写法:

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。

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();

思路二

如果希望被jackson正确序列化,或许使用 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 得到 一个 JSONOject,再添加一些字段,比较麻烦,但是也能解决问题。

思路三

jackson那么强大,直接让jackson支持protobuf行不行?

答案是行。

找到jackson的 github项目页面
然后 发现,readme下方有

点进入查看 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中呢?

  1. 引入上述第三方jackson-datatype-protobuf的依赖
  2. 在项目中引入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.modulesToInstall(ProtobufModule.class);
    };
  }

}

完美解决

01-26 10:35
查看更多