zoukankan      html  css  js  c++  java
  • 让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中呢?

    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.propertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);//如果字段都是驼峰命名规则,需要这一句
          jacksonObjectMapperBuilder.modulesToInstall(ProtobufModule.class);
        };
      }
    
    }
    

    完美解决

  • 相关阅读:
    希尔排序
    快速排序
    归并排序
    插入排序
    简单选择排序
    冒泡排序
    算法之时间复杂度和空间复杂度
    数据结构与算法思维导图
    rootfs根文件系统
    kernel 2.6.35.7向S5PV210移植
  • 原文地址:https://www.cnblogs.com/slankka/p/11653114.html
Copyright © 2011-2022 走看看