使用协议缓冲区和内部数据模型

Posted

技术标签:

【中文标题】使用协议缓冲区和内部数据模型【英文标题】:Working with Protocol Buffers and internal data models 【发布时间】:2012-03-05 13:39:45 【问题描述】:

我有一个Picture 的现有内部数据模型,如下:

package test.model;
public class Picture 

  private int height, width;
  private Format format;

  public enum Format 
    JPEG, BMP, GIF
  

  // Constructor, getters and setters, hashCode, equals, toString etc.

我现在想使用protocol buffers 对其进行序列化。我写了一个 Picture.proto 文件,它反映了Picture 类的字段,并在test.model.protobuf 包下编译了代码,类名为PictureProtoBuf

package test.model.protobuf;

option java_package = "test.model.protobuf";
option java_outer_classname = "PictureProtoBuf";

message Picture 
  enum Format 
    JPEG = 1;
    BMP = 2;
    GIF = 3;
  
  required uint32 width = 1;
  required uint32 height = 2;
  required Format format = 3;

现在我假设如果我有一个 Picture 想要序列化并发送到某处,我必须创建一个 PictureProtoBuf 对象并映射所有字段,如下所示:

Picture p = new Picture(100, 200, Picture.JPEG);
PictureProtoBuf.Picture.Builder output = PictureProtoBuf.Picture.newBuilder();
output.setHeight(p.getHeight());
output.setWidth(p.getWidth());

当我的数据模型中有一个枚举时,我就会陷入困境。我现在使用的丑陋方式是:

output.setFormat(PictureProtoBuf.Picture.Format.valueOf(p.getFormat().name());

但是,这很容易损坏,并且依赖于枚举名称在我的内部数据模型和协议缓冲区数据模型之间保持一致(这不是一个很好的假设,因为 .proto 文件中的枚举名称需要是唯一的)。如果来自内部模型的 .name() 调用与 protobuf 生成的枚举名称不匹配,我可以看到我必须在枚举上手工制作 switch 语句。

我想我的问题是我是否以正确的方式处理这件事?我是否应该放弃我的内部数据模型 (test.model.Picture) 以支持 protobuf 生成的模型 (test.model.protobuf.PictureProtoBuf)?如果是这样,我如何实现我在内部数据模型中所做的一些细节(例如hashCode()equals(Object)toString() 等)?

【问题讨论】:

我没有尝试过(纯粹是因为我主要是 .NET 人员),但我相信 protostuff 允许您继续使用现有模型。 . @MarcGravell - 感谢您的建议。你的预感是正确的; protostuff 完全符合我的要求,但在后端保留了协议缓冲区(但尚未测试其与 Google protobuf 库的兼容性)。 【参考方案1】:

虽然现有的答案很好,但我决定更进一步地参考Marc Gravell 的建议来研究 protostuff。

您可以使用 protostuff runtime module 以及动态 ObjectSchema 在运行时为您的内部数据模型创建模式

我的代码现在简化为:

// Do this once
private static Schema<Picture> schema = RuntimeSchema.getSchema(Picture.class);
private static final LinkedBuffer buffer = LinkedBuffer.allocate(DEFAULT_BUFFER_SIZE);

// For each Picture you want to serialize...
Picture p = new Picture(100, 200, Picture.JPEG);
byte[] result = ProtobufIOUtil.toByteArray(p, schema, buffer);
buffer.clear();
return result;

当您的内部数据模型中有大量属性时,这是对 Google protobuf 库(请参阅我的问题)的巨大改进。也没有我可以检测到的速度损失(无论如何,我的用例!)

【讨论】:

【参考方案2】:

如果您可以控制内部数据模型,则可以修改 test.model.Picture 以便枚举值知道它们对应的 protobuf 等价物,可能会将对应关系传递给您的枚举构造函数。

例如,使用Guava'sBiMap(具有唯一值的双向映射),我们会得到类似

enum ProtoEnum  // we don't control this
  ENUM1, ENUM2, ENUM3;


enum MyEnum 
  ONE(ProtoEnum.ENUM1), TWO(ProtoEnum.ENUM2), THREE(ProtoEnum.ENUM3);

  static final ImmutableBiMap<MyEnum, ProtoEnum> CORRESPONDENCE;

  static 
    ImmutableBiMap.Builder<ProtoEnum, MyEnum> builder = ImmutableBiMap.builder();
    for (MyEnum x : MyEnum.values()) 
      builder.put(x.corresponding, x);
    
    CORRESPONDENCE = builder.build();
  

  private final ProtoEnum corresponding;

  private MyEnum(ProtoEnum corresponding) 
    this.corresponding = corresponding;
  

然后,如果我们想查找与ProtoEnum 对应的MyEnum,我们只需执行MyEnum.CORRESPONDENCE.get(protoEnum),反之,我们只需执行MyEnum.CORRESPONDENCE.inverse().get(myEnum)myEnum.getCorresponding()

【讨论】:

感谢您的回答。我想我掌握了这个概念,但我不确定我将如何实现它。你介意删掉一些代码吗?【参考方案3】:

一种方法是只保留生成的枚举:

package test.model;
public class Picture 

  private int height, width;
  private PictureProtoBuf.Picture.Format format;

 // Constructor, getters and setters, hashCode, equals, toString etc.

我已经用过几次了,在你的情况下它可能有意义,也可能没有意义。但是,从不推荐使用 protobuf 生成的类作为数据模型(或扩展它们以添加功能)。

【讨论】:

以上是关于使用协议缓冲区和内部数据模型的主要内容,如果未能解决你的问题,请参考以下文章

协议缓冲区和 OO 设计

网络七层模型

IO操作与IO模型

浅谈libevent的使用--事件和数据缓冲

网络IO模型

Java NIO之缓冲区