«1. Обзор

В этой статье мы рассмотрим буфер протокола Google (protobuf) — широко известный формат двоичных данных, не зависящий от языка. Мы можем определить файл с протоколом, а затем, используя этот протокол, мы можем генерировать код на таких языках, как Java, C++, C#, Go или Python.

Это вводная статья о самом формате; если вы хотите узнать, как использовать формат с веб-приложением Spring, ознакомьтесь с этой статьей.

2. Определение зависимостей Maven

Чтобы использовать буферы протокола в Java, нам нужно добавить зависимость Maven к protobuf-java:

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>${protobuf.version}</version>
</dependency>

<properties>
    <protobuf.version>3.2.0</protobuf.version>
</properties>

3. Определение протокола

Давайте начнем с примера . Мы можем определить очень простой протокол в формате protobuf:

message Person {
    required string name = 1;
}

Это протокол простого сообщения типа Person, который имеет только одно обязательное поле — имя, имеющее строковый тип.

Давайте рассмотрим более сложный пример определения протокола. Допустим, нам нужно хранить информацию о человеке в формате protobuf:

package protobuf;

package protobuf;

option java_package = "com.baeldung.protobuf";
option java_outer_classname = "AddressBookProtos";

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

    repeated string numbers = 4;
}

message AddressBook {
    repeated Person people = 1;
}

Наш протокол состоит из двух типов данных: Person и AddressBook. После создания кода (подробнее об этом в следующем разделе) эти классы будут внутренними классами внутри класса AddressBookProtos.

Когда мы хотим определить обязательное поле — это означает, что создание объекта без такого поля вызовет исключение, нам нужно использовать ключевое слово required.

Создание поля с необязательным ключевым словом означает, что это поле не нужно задавать. Повторяющееся ключевое слово представляет собой тип массива переменного размера.

Все поля проиндексированы — поле, обозначенное номером 1, будет сохранено как первое поле в двоичном файле. Поле, отмеченное цифрой 2, будет сохранено следующим и так далее. Это дает нам лучший контроль над расположением полей в памяти.

4. Генерация кода Java из файла Protobuf

Определив файл, мы можем сгенерировать из него код.

Во-первых, нам нужно установить protobuf на нашу машину. Сделав это, мы можем сгенерировать код, выполнив команду protoc:

protoc -I=. --java_out=. addressbook.proto

Команда protoc сгенерирует выходной файл Java из нашего файла addressbook.proto. Параметр -I указывает каталог, в котором находится прото-файл. java-out указывает каталог, в котором будет создан сгенерированный класс.

Сгенерированный класс будет иметь сеттеры, геттеры, конструкторы и построители для наших определенных сообщений. Он также будет иметь несколько полезных методов для сохранения файлов protobuf и их десериализации из двоичного формата в класс Java.

5. Создание экземпляра сообщений, определенных Protobuf

Мы можем легко использовать сгенерированный код для создания Java-экземпляра класса Person:

String email = "[email protected]";
int id = new Random().nextInt();
String name = "Michael Program";
String number = "01234567890";
AddressBookProtos.Person person =
  AddressBookProtos.Person.newBuilder()
    .setId(id)
    .setName(name)
    .setEmail(email)
    .addNumbers(number)
    .build();

assertEquals(person.getEmail(), email);
assertEquals(person.getId(), id);
assertEquals(person.getName(), name);
assertEquals(person.getNumbers(0), number);

Мы можем создать текучий построитель, используя метод newBuilder() на нужный тип сообщения. После настройки всех необходимых полей мы можем вызвать метод build() для создания экземпляра класса Person.

6. Сериализация и десериализация Protobuf

Создав экземпляр класса Person, мы хотим сохранить его на диске в двоичном формате, совместимом с созданным протоколом. Допустим, мы хотим создать экземпляр класса AddressBook и добавить в этот объект одного человека.

Затем мы хотим сохранить этот файл на диске — в автоматически сгенерированном коде есть вспомогательный метод writeTo(), который мы можем использовать:

AddressBookProtos.AddressBook addressBook 
  = AddressBookProtos.AddressBook.newBuilder().addPeople(person).build();
FileOutputStream fos = new FileOutputStream(filePath);
addressBook.writeTo(fos);

После выполнения этого метода наш объект будет сериализован. в двоичный формат и сохраняется на диске. Чтобы загрузить эти данные с диска и десериализовать их обратно в объект AddressBook, мы можем использовать метод mergeFrom():

AddressBookProtos.AddressBook deserialized
  = AddressBookProtos.AddressBook.newBuilder()
    .mergeFrom(new FileInputStream(filePath)).build();
 
assertEquals(deserialized.getPeople(0).getEmail(), email);
assertEquals(deserialized.getPeople(0).getId(), id);
assertEquals(deserialized.getPeople(0).getName(), name);
assertEquals(deserialized.getPeople(0).getNumbers(0), number);

7. Заключение

В этой быстрой статье мы представили стандарт для описания и хранения данных. в двоичном формате — Google Protocol Buffer.

Мы создали простой протокол, создали экземпляр Java, соответствующий заданному протоколу. Далее мы увидели, как сериализовать и десериализовать объекты с помощью protobuf.

Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub — это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.