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

Java 使用协议缓冲区和内部数据模型,java,protocol-buffers,datamodel,Java,Protocol Buffers,Datamodel,我有一个图片的现有内部数据模型,如下所示: 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. } 现在我想使用。我

我有一个
图片
的现有内部数据模型,如下所示:

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.
}
现在我想使用。我编写了一个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;
}
现在我假设如果我有一个
图片
,我想序列化并发送到某个地方,我必须创建一个
图片protobuf
对象并映射所有字段,如下所示:

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()
,等等)?

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

例如,使用(具有唯一值的双向映射),我们得到如下结果

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;
  }
}
enum ProtoEnum{//我们不控制它
枚举1、枚举2、枚举3;
}
髓鞘{
一个(ProtoEnum.ENUM1),两个(ProtoEnum.ENUM2),三个(ProtoEnum.ENUM3);
静态最终不可变BIMAP对应;
静止的{
ImmutableBiMap.Builder=ImmutableBiMap.Builder();
for(MyEnum x:MyEnum.values()){
构建器放置(x.对应,x);
}
通信=builder.build();
}
私有最终原型对应;
私有MyEnum(对应于ProtoEnum){
这个对应的=对应的;
}
}

然后,如果我们想查找对应于
ProtoEnum
MyEnum
,我们只需执行
MyEnum.corrective.get(ProtoEnum)
,反之,我们只需执行
MyEnum.corrective.inverse().get(MyEnum)
MyEnum.getcorrective()

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

package test.model;
public class Picture {

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

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

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

虽然现有的答案很好,但我决定进一步研究protostuff的建议

您可以使用protostuff和DynamicObjectSchema在运行时为内部数据模型创建模式

我的代码现在简化为:

// 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;
//只做一次
私有静态模式Schema=RuntimeSchema.getSchema(Picture.class);
私有静态最终LinkedBuffer buffer=LinkedBuffer.allocate(默认缓冲区大小);
//对于每个要序列化的图片。。。
图片p=新图片(100200,Picture.JPEG);
byte[]result=ProtobufIOUtil.toByteArray(p,schema,buffer);
buffer.clear();
返回结果;

当您的内部数据模型中有很多属性时,这是对GoogleProtobuf库(参见我的问题)的极大改进。我也没有发现速度损失(不管怎样,我的用例也是如此!)

我还没有尝试过(纯粹因为我主要是.NET用户),但我相信这可以让你继续使用现有的模型。@MarcGravell-谢谢你的建议。你的直觉是正确的;protostuff做的和我想做的完全一样,但是在后端保留了协议缓冲区(不过还没有测试它与Google protobuf库的兼容性)。谢谢你的回答。我想我已经掌握了这个概念,但我不确定如何实施它。你介意删掉一些代码吗?