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

    完美解决

  • 相关阅读:
    ACM ICPC 2008–2009 NEERC MSC A, B, C, G, L
    POJ 1088 滑雪 DP
    UVA 11584 最短回文串划分 DP
    POJ 2531 Network Saboteur DFS+剪枝
    UVa 10739 String to Palindrome 字符串dp
    UVa 11151 Longest Palindrome 字符串dp
    UVa 10154 Weights and Measures dp 降维
    UVa 10271 Chopsticks dp
    UVa 10617 Again Palindrome 字符串dp
    UVa 10651 Pebble Solitaire 状态压缩 dp
  • 原文地址:https://www.cnblogs.com/slankka/p/11653114.html
Copyright © 2011-2022 走看看