Определить класс ProtoBuf из байтового массива

Я пишу программу, которая работает с двумя прото-сообщениями, мне нужно обработать byte [], отправленный из разных источников, который отправляет либо сообщение foo, либо сообщение bar. Поскольку я не могу понять, какому сообщению он принадлежит, я использовал Any Class (поставляется вместе с protobuf), чтобы проанализировать массив байтов и найти, к какому классу он принадлежит, но обнаружил ошибку времени компиляции. Есть ли какой-нибудь другой метод, который я могу использовать, чтобы определить, добавляю ли я больше классов протокольных сообщений в будущем?

//Foo.proto

syntax = "proto3";

option java_outer_classname = "FooProto";

message Foo {
    int32 a = 1;
}

и второй прото

//Bar.proto
syntax = "proto3";

option java_outer_classname = "BarProto";

message Bar {
    int32 b = 1;
}

Код:

Any anyEvent = Any.parseFrom(protoBytes);
if (any.is(Foo.class)
{
  Foo foo = any.unpack(Foo.class);
  ...
} else {
  Bar bar = any.unpack(Bar.class);
  ...
}

Ошибка в операторе if при попытке вызвать any.is ():

Метод (Class ‹T>) в типе Any неприменим для аргументов (Class‹ Foo>)


person Santhosh    schedule 06.02.2020    source источник


Ответы (1)


Any не означает «любой»; это означает «тип, сериализованный через Any». Если вы не сохранили его с Any: вы не можете декодировать его с помощью Any.

Ключевым моментом здесь является то, что protobuf не включает метаданные типа в полезные данные сообщения. Если у вас есть большой двоичный объект, вы обычно не можете узнать, что это за тип сообщения. Any решает эту проблему, кодируя тип сообщения в сообщении-оболочке, но здесь этого не будет.


Если в вашем проекте должен быть API, который принимает два разных типа сообщений, отличных от Any, без предварительного знания о том, что это такое: вероятно, у вас плохой дизайн. Потому что этот не работает с protobuf. На проводе нет буквально никакой разницы между Foo с a=42 и Bar с b=42; полезные данные идентичны:

Foo с a=42 - это байты 08 2A; Bar с b=42 - это байты 08 2A. 08 означает «поле 1, закодированное как варинт», 2A - это варинт с необработанным значением 42.

Лучшим вариантом может быть сообщение-оболочка, специфичное для вашего сценария:

message RootMessage {
  oneof test_oneof {
     Foo foo = 1;
     Bar bar = 2;
  }
}

Это добавляет слой оболочки аналогичный тому, как работает Any, но гораздо более эффективно - он просто знает, как различать ваши известные типы как целое число, вместо того, чтобы обрабатывать все возможные типы (как корневой тип имя).

person Marc Gravell    schedule 06.02.2020
comment
Спасибо за информацию о Any, проблема в том, что сообщение foo уже создано из источника, и дизайн не может быть изменен. Есть ли другое решение? - person Santhosh; 06.02.2020
comment
@Santosh это только два типа, как показано? В этом сценарии я мог бы добавить поле перечисления (поле 2) в Bar со значением по умолчанию 0 = Foo, 1 = Bar и включить это значение с 1 = Bar для нового производителя - декодировать все как a Bar, но если в поле enum указано Foo, интерпретируйте его как Foo (просто прочтите b и представьте, что это a). Это работает только потому, что тип поля идентичен; если у Foo есть int32 a = 1, а у Bar есть строка b = 1, это не сработает. - person Marc Gravell; 06.02.2020
comment
@santosh главное помнить, что имена полей не имеют значения в бинарном протоколе; они не отправляются и не используются для декодирования; имеют значение только числа (и их типы полей) - person Marc Gravell; 06.02.2020
comment
Определенные типы данных отличаются от того, что я упомянул в вопросе, типы полей не совпадают, что затрудняет определение класса, к которому они принадлежат. - person Santhosh; 06.02.2020
comment
@Santhosh Ага, это очень сложно; в таком случае лучше всего использовать API-интерфейс считывателя для проверки полезной нагрузки и просмотра имеющихся полей и типов проводов, чтобы увидеть, можете ли вы вручную определить, что есть что; в конечном счете, protobuf не очень подходит для того, что вы здесь пытаетесь сделать - он хочет заранее знать целевой тип - person Marc Gravell; 06.02.2020
comment
В частности, @Santhosh: CodedInputStream - выглядит как readTag() и skipField(); обратите внимание, что тег представляет собой смесь номера поля и типа провода с использованием двоичной математики ({tag}={fieldNumber} << 3 | {wireType}) - person Marc Gravell; 06.02.2020