SpringBoot使用protobuf生成的model进行传参无法序列化

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot使用protobuf生成的model进行传参无法序列化相关的知识,希望对你有一定的参考价值。

参考技术A 配置后不生效,报错信息如下:

最开始怀疑是ProtobufHttpMessageConverter没有注册到HttpMessageConverter的列表中,于是开始跟踪源码发现HttpMessageConverter列表中已经添加上了:

既然已经添加进去了,那为什么不走转换器的逻辑,继续往下走发现:

converter.canWrite(valueType, selectedMediaType)这个方法返回false导致body没有进行适配,下面进行判空逻辑后就会抛出异常:

继续跟下去,canWrite是调用了父类org.springframework.http.converter.AbstractHttpMessageConverter#canWrite:

而ProtobufHttpMessageConverter其实已经重写了supports与canWrite方法:

这里发现根源其实是Message.class.isAssignableFrom(clazz)返回的false。那么难道protobuf生成的model不是com.google.protobuf.Message的子类吗?

查看生成的java文件发现model确实不是继承的GeneratedMessageV3,而是继承了GeneratedMessageLite。至此发现了导致问题的根源。

使用 protobuf-net.Grpc 生成通用服务的 .proto 文件

【中文标题】使用 protobuf-net.Grpc 生成通用服务的 .proto 文件【英文标题】:Generate .proto file of generic services with protobuf-net.Grpc 【发布时间】:2021-09-12 19:28:23 【问题描述】:

我正在尝试生成此结构的 .proto:

-- 模型 --基础模型

[DataContract]
public abstract class Base

   [ProtoMember(1)]
   public string Id  get; set; 

   [ProtoMember(2, DataFormat = DataFormat.WellKnown)]
   public DateTime CreatedDate  get; private set;  = DateTime.UtcNow;

   [ProtoMember(3, DataFormat = DataFormat.WellKnown)]
   public DateTime UpdatedDate  get; set;  = DateTime.UtcNow;
         

待办事项模型

[ProtoContract]
public class Todo : Base

   [ProtoMember(1)]
   public string Title  get; set; 

   [ProtoMember(2)]
   public string Content  get; set; 
 
   [ProtoMember(3)]
   public string Category  get; set; 
      

加上这一行:

RuntimeTypeModel.Default[typeof(Base)].AddSubType(42, typeof(Todo));

-- 合同 --基础合同

[ServiceContract]
public interface IBaseService<T>

   // CREATE
   [OperationContract]
   Task<RStatus> CreateOneAsync(T request,CallContext context = default);
   
   // FIND
   [OperationContract]
   ValueTask<T> GetById(UniqueIdentification request,CallContext context = default);
        

待办事项合约

[ServiceContract]
public interface ITodoService : IBaseService<Todo>

   // FIND        
   [OperationContract]
   ValueTask<Todo> GetOneByQueryAsync(Query query, CallContext context = default);
          

通过这种通用方法,我试图防止重复代码。

-- Startup.cs --

     ...
endpoints.MapGrpcService<TodoService>();
endpoints.MapCodeFirstGrpcReflectionService();
     ...       

所以,当我运行这个时:

var schema = generator.GetSchema<ITodoService>();

我在 .proto 文件中得到这个输出:

syntax = "proto3";
package Nnet.Contracts;
import "google/protobuf/timestamp.proto";

message Base 
   string Id = 1;
   .google.protobuf.Timestamp CreatedDate = 2;
   .google.protobuf.Timestamp UpdatedDate = 3;
   oneof subtype 
     Todo Todo = 42;
   

message IEnumerable_Todo 
   repeated Base items = 1;

message Query 
   string Filter = 1;

message Todo 
   string Title = 1;
   string Content = 2;
   string Category = 3;

service TodoService 
   rpc GetOneByQuery (Query) returns (Base);

    

在 .proto 文件部分 service Todoservice 中,我缺少 Base 合约中的其他两个函数。还有,函数rpc GetOneByQuery (Query) returns (Base);的返回类型不对,应该是Todo。

有什么建议吗?

【问题讨论】:

【参考方案1】:

另外,函数rpc GetOneByQuery(Query)的返回类型返回(Base);错了,应该是Todo。

不,这是正确的; protobuf 本身没有继承的概念 - protobuf-net 必须填充它,它使用封装来实现,因此带有 oneof subtypeBase 具有 Todo。在您的情况下,我们希望传递的东西实际上总是将 as 解析为 Todo,但 .proto 模式语言没有语法来表达这一点。我们可以在这里做的最好的事情是在生成的 .proto 中包含一个额外的注释,说 // return type will always be a Todo 或类似的。

我缺少基本合约中的其他两个功能

这里目前还没有很好地支持服务继承和通用服务;同样,这些概念在 .proto 或 gRPC 中通常没有匹配的隐喻,protobuf-net 需要发明一些合适的东西;迄今为止,我还没有坐下来思考过任何这样的计划或其中的含义。从根本上说,这里的问题是服务合约和名称用于构造路由/url;在谈论单个服务合同和方法时,这很好 - 但在谈论服务继承和泛型时,唯一地识别您正在谈论的服务以及应该如何在路由和实现之间映射变得更加复杂(事实上​​,.proto 语法)。我对这里的想法完全持开放态度 - 迄今为止,它还不是关键路径要求。

【讨论】:

【参考方案2】:

好吧,目前我所有的服务都在 C# 中,但是如果我们必须将 .proto 文件共享给 C# 应用程序以外的其他应用程序,那么将来支持这种接口继承模式会很棒。我不确定是否可以粘贴可以帮助您支持此功能的代码...所以,这是代码:我正在跳过模型-- 合同--

//Base Contract
public interface IBaseService<T>

    Task<RStatus> CreateOne(T request);

    ValueTask<T> GetById(UniqueIdentification request);


//Product Contract
public interface IProductService<T> : IBaseService<T>

    Task<T> GetByBrandName(string request);

    ValueTask<T> GetByName(string request);


    //Audio Contract
public interface IAudioService<T>

    Task<T> GetBySoundQuality(int request);



//Headphones Contract
public interface IHeadphonesService : IProductService<Headphones>, IAudioService<Headphones>

    Task<Headphones> GetByBluetoothOption(bool request);
             

-- PROGRAM.CS --

static void Main(string[] args)

    foreach (var type in TypesToGenerateForType(typeof(IHeadphonesService)))
    
        Console.WriteLine($"Type: type \n");
    


public static IEnumerable<Type> TypesToGenerateForType(Type type)

    foreach (var interfaceType in type.FindInterfaces((ignored, data) => true, null))
    
        foreach (var dm in interfaceType.GetMethods())
        
            Console.WriteLine($"Method Name: dm");
        
        yield return interfaceType;
    
    foreach (var tm in type.GetMethods())
    
        Console.WriteLine($"Method Name: tm");
    
    yield return type;
      
    

输出:

Method Name: System.Threading.Tasks.Task`1[TestIntInh.Shared.Models.Headphones] GetByBrandName(System.String)
Method Name: System.Threading.Tasks.ValueTask`1[TestIntInh.Shared.Models.Headphones] GetByName(System.String)
Type: TestIntInh.Shared.Contracts.IProductService`1[TestIntInh.Shared.Models.Headphones 

Method Name: System.Threading.Tasks.Task`1[TestIntInh.Shared.Models.RStatus] CreateOne(TestIntInh.Shared.Models.Headphones)
Method Name: System.Threading.Tasks.ValueTask`1[TestIntInh.Shared.Models.Headphones] GetById(TestIntInh.Shared.Models.UniqueIdentification)
Type: TestIntInh.Shared.Contracts.IBaseService`1[TestIntInh.Shared.Models.Headphones] 

Method Name: System.Threading.Tasks.Task`1[TestIntInh.Shared.Models.Headphones] GetBySoundQuality(Int32)
Type: TestIntInh.Shared.Contracts.IAudioService`1[TestIntInh.Shared.Models.Headphones] 

Method Name: System.Threading.Tasks.Task`1[TestIntInh.Shared.Models.Headphones] GetByBluetoothOption(Boolean)
Type: TestIntInh.Shared.Contracts.IHeadphonesService        
    

我认为您可以使用此构建适当的 .proto 文件。 FindInterfaces 几乎可以满足您的所有需求。

【讨论】:

以上是关于SpringBoot使用protobuf生成的model进行传参无法序列化的主要内容,如果未能解决你的问题,请参考以下文章

ProtoBuf及整合到SpringBoot

Netty+SpringBoot+protobuf3 搭建socket游戏网络层

让SpringBoot的jackson支持JavaBean嵌套的protobuf

QT中使用MinGW 编译的protobuf库--包含库的生成和使用

Protobuf 文件生成工具 Prototool 命令详解

protobuf 和 .net - 如何使用生成的 .cs 文件?