如何确定字节数组对应的protobuf消息?

14

我希望使用protobuf代替Json在消息队列之间进行通信。

当只有一个proto消息时,我知道该如何处理它。

假设proto文件为:

//person.proto
syntax = "proto3";

option java_outer_classname = "PersonProto";

message Person {
    int32 id = 2;
    string name = 1;
    string email = 3;
}

现在,我可以用以下方法来处理它:

PersonProto.Person person = PersonProto.Person.newBuilder()
        .setEmail("123@test.com")
        .setId(1)
        .setName("name-test")
        .build();

byte[] bytes = person.toByteArray();

//Transfer from publisher to consumer between message queue.

//I can deserialise it, because i know the proto message is Person.
PersonProto.Person.parseFrom(bytes);

但如果有多个proto消息呢?

假设还有另一个名为Address的proto消息。

syntax = "proto3";

option java_outer_classname = "PersonProto";

message Person {
    int32 id = 2;
    string name = 1;
    string email = 3;
}

message Address {
    string address = 1;
}

当消费者从消息队列中接收到字节数组时,如何知道它是哪个 Proto 消息?以及如何对字节数组进行反序列化?

2个回答

25

Protobuf 3引入了Any的概念,它可以类似于@AdamCozzette所解释的顶级消息模式进行操作。

在写入方面,您将消息打包在一个Any中:

Person person = ...
Any any = Any.pack(person);

out.write(any.toByteArray());

在读取端,您将数据读入到一个Any中,并根据您感兴趣的类型进行切换:

Any any = Any.parseFrom(in);

if (any.is(Person.class)
{
  Person person = any.unpack(Person.class);
  ...
}
else if (any.is(Address.class);
{
  Address address = any.unpack(Address.class);
  ...
}
else
{
  //Handle unknown message
}

使用Any可以避免特殊消息类型(顶级消息),但也会降低类型安全性,因为您可能会收到消费代码无法处理的消息。


任何对象都没有.parseFrom方法。 - Grisgram
在我的 IDE 和 JavaDocs 中,Any 类型有一个 parseFrom 方法 - https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/Any#parseFrom-java.io.InputStream- - Nick Holt
似乎在最新版本中已经发生了变化。现在是any.Parser.ParseFrom。 - Grisgram

8

Protocol Buffers不是自描述的,因此一般情况下,在不知道预期模式的情况下,无法解释序列化的protobuf内容。

在您的情况下,我建议使用oneof字段。您可以为队列消息使用一个单一的顶级消息类型,并让它包含一个oneof字段,其中包含一个人员或地址:

message TopLevelMessage {
  oneof inner_message {
    Person person = 1;
    Address address = 2;
  }
}

消费代码需要使用类似以下的switch语句来获取内部消息:
TopLevelMessage topLevelMessage = TopLevelMessage.parseFrom(...);

switch (topLevelMessage.getInnerMessageCase()) 
{
  case PERSON:
    Person person = topLevelMessage.getPerson();
    ...
    break;

  case ADDRESS:
    Address address = topLevelMessage.getAddress();
    ...
    break;

  default:
     ... 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接