На этой странице описывается, какой код Dart генерирует компилятор буфера протокола для любого заданного определения протокола. Любые различия между сгенерированным кодом proto2 и proto3 выделены — обратите внимание, что эти различия заключаются в сгенерированном коде, как описано в этом документе, а не в базовом API, который одинаков в обеих версиях. Вы должны прочитать руководство по языку proto2 и/или руководство по языку proto3, прежде чем читать этот документ.
Вызов компилятора
Компилятору буфера протокола требуется подключаемый модуль для генерации кода Dart . Установка его в соответствии с инструкциями предоставляет protoc-gen-dart
, который protoc
использует при вызове с флагом командной строки --dart_out
. Флаг --dart_out
сообщает компилятору, куда записывать исходные файлы Dart. Для ввода файла .proto
компилятор создает среди прочего файл .pb.dart
.
Имя файла .pb.dart
вычисляется путем взятия имени файла .proto
и внесения двух изменений:
- Расширение (
.proto
) заменяется на.pb.dart
. Например, файл с именемfoo.proto
приводит к выходному файлу с именемfoo.pb.dart
. - Прото-путь (указанный флагом командной строки
--proto_path
или-I
) заменяется выходным путем (указанным флагом--dart_out
).
Например, когда вы вызываете компилятор следующим образом:
protoc --proto_path=src --dart_out=build/gen src/foo.proto src/bar/baz.proto
компилятор прочитает файлы src/foo.proto
и src/bar/baz.proto
. Он производит: build/gen/foo.pb.dart
и build/gen/bar/baz.pb.dart
. Компилятор автоматически создает каталог build/gen/bar
, если это необходимо, но не создает build
или build/gen
; они должны уже существовать.
Сообщения
Учитывая простое объявление сообщения:
message Foo {}
Компилятор буфера протокола генерирует класс с именем Foo
, который расширяет класс GeneratedMessage
.
Класс GeneratedMessage
определяет методы, позволяющие проверять, обрабатывать, читать или записывать все сообщение целиком. В дополнение к этим методам класс Foo
определяет следующие методы и конструкторы:
-
Foo()
: конструктор по умолчанию. Создает экземпляр, в котором все одиночные поля не заданы, а повторяющиеся поля пусты. -
Foo.fromBuffer(...)
: создаетFoo
из сериализованных данных буфера протокола, представляющих сообщение. -
Foo.fromJson(...)
: создаетFoo
из строки JSON, кодирующей сообщение. -
Foo clone()
: создает глубокое клонирование полей в сообщении. -
Foo copyWith(void Function(Foo) updates)
: создает доступную для записи копию этого сообщения, применяет к немуupdates
и помечает копию как доступную только для чтения перед ее возвратом. -
static Foo create()
: фабричная функция для создания одногоFoo
. -
static PbList<Foo> createRepeated()
: фабричная функция для создания списка, реализующего изменяемое повторяющееся поле элементовFoo
. -
static Foo getDefault()
: возвращает одноэлементный экземплярFoo
, который идентичен вновь созданному экземпляру Foo (поэтому все единичные поля не установлены, а все повторяющиеся поля пусты).
Вложенные типы
Сообщение может быть объявлено внутри другого сообщения. Например:
message Foo { message Bar { } }
В этом случае компилятор генерирует два класса: Foo
и Foo_Bar
.
Поля
В дополнение к методам, описанным в предыдущем разделе, компилятор буфера протокола создает методы доступа для каждого поля, определенного в сообщении в файле .proto
.
Обратите внимание, что сгенерированные имена всегда используют верблюжий регистр, даже если имя поля в файле .proto
использует нижний регистр с подчеркиванием ( как и должно быть ). Преобразование регистра работает следующим образом:
- Для каждого подчеркивания в имени подчеркивание удаляется, а следующая буква делается заглавной.
- Если к имени будет прикреплен префикс (например, «имеет»), первая буква будет заглавной. В противном случае это нижний регистр.
Таким образом, для поля foo_bar_baz
геттер становится get fooBarBaz
, а метод с префиксом has
будет hasFooBarBaz
.
Сингулярные примитивные поля (proto2)
Для любого из этих определений полей:
optional int32 foo = 1; required int32 foo = 1;
Компилятор сгенерирует следующие методы доступа в классе сообщения:
-
int get foo
: возвращает текущее значение поля. Если поле не задано, возвращает значение по умолчанию. -
bool hasFoo()
: возвращаетtrue
, если поле установлено. -
set foo(int value)
: устанавливает значение поля. После вызоваhasFoo()
вернетtrue
, аget foo
вернетvalue
. -
void clearFoo()
: очищает значение поля. После вызоваhasFoo()
вернетfalse
, аget foo
вернет значение по умолчанию.
Для других простых типов полей соответствующий тип Dart выбирается согласно таблице типов скалярных значений . Для типов сообщения и перечисления тип значения заменяется классом сообщения или перечисления.
Сингулярные примитивные поля (proto3)
Для этого определения поля:
int32 foo = 1;
Компилятор сгенерирует следующие методы доступа в классе сообщения:
-
int get foo
: возвращает текущее значение поля. Если поле не задано, возвращает значение по умолчанию. -
set foo(int value)
: устанавливает значение поля. После этогоget foo
вернетvalue
. -
void clearFoo()
: очищает значение поля. После этогоget foo
вернет значение по умолчанию.
Отдельные поля сообщений
Учитывая тип сообщения:
message Bar {}Для сообщения с полем
Bar
:// proto2 message Baz { optional Bar bar = 1; // The generated code is the same result if required instead of optional. } // proto3 message Baz { Bar bar = 1; }
Компилятор сгенерирует следующие методы доступа в классе сообщения:
-
Bar get bar
: возвращает текущее значение поля. Если поле не задано, возвращает значение по умолчанию. -
set bar(Bar value)
: устанавливает значение поля. После этогоhasBar()
вернетtrue
, аget bar
вернетvalue
. -
bool hasBar()
: возвращаетtrue
, если поле установлено. -
void clearBar()
: очищает значение поля. После этогоhasBar()
вернетfalse
, аget bar
вернет значение по умолчанию. -
Bar ensureBar()
: Устанавливаетbar
в пустой экземпляр, еслиhasBar()
возвращаетfalse
, а затем возвращает значениеbar
. После этогоhasBar()
вернетtrue
.
Повторяющиеся поля
Для этого определения поля:
repeated int32 foo = 1;
Компилятор сгенерирует:
-
List<int> get foo
: возвращает список, поддерживающий поле. Если поле не задано, возвращает пустой список. Изменения в списке отражаются в поле.
Поля Int64
Для этого определения поля:
// proto2 optional int64 bar = 1; // proto3 int64 bar = 1;
Компилятор сгенерирует:
-
Int64 get bar
: возвращает объектInt64
, содержащий значение поля.
Обратите внимание, что Int64
не встроен в основные библиотеки Dart. Для работы с этими объектами может потребоваться импорт библиотеки Dart fixnum
:
import 'package:fixnum/fixnum.dart';
Поля карты
Учитывая такое определение поля map
:
map<int32, int32> map_field = 1;
Компилятор сгенерирует следующий геттер:
-
Map<int, int> get mapField
: возвращает карту Dart, поддерживающую поле. Если поле не задано, возвращает пустую карту. Изменения карты отражаются в поле.
Любой
Учитывая Any
поле, подобное этому:
import "google/protobuf/any.proto"; message ErrorStatus { string message = 1; google.protobuf.Any details = 2; }
В нашем сгенерированном коде геттер для поля details
возвращает экземпляр com.google.protobuf.Any
. Это предоставляет следующие специальные методы для упаковки и распаковки значений Any
:
/// Unpacks the message in [value] into [instance]. /// /// Throws a [InvalidProtocolBufferException] if [typeUrl] does not correspond /// to the type of [instance]. /// /// A typical usage would be `any.unpackInto(new Message())`. /// /// Returns [instance]. T unpackInto<T extends GeneratedMessage>(T instance, {ExtensionRegistry extensionRegistry = ExtensionRegistry.EMPTY}); /// Returns `true` if the encoded message matches the type of [instance]. /// /// Can be used with a default instance: /// `any.canUnpackInto(Message.getDefault())` bool canUnpackInto(GeneratedMessage instance); /// Creates a new [Any] encoding [message]. /// /// The [typeUrl] will be [typeUrlPrefix]/`fullName` where `fullName` is /// the fully qualified name of the type of [message]. static Any pack(GeneratedMessage message, {String typeUrlPrefix = 'type.googleapis.com'});
Один из
Учитывая такое определение oneof
:
message Foo { oneof test { string name = 1; SubMessage sub_message = 2; } }
Компилятор сгенерирует следующий тип перечисления Dart:
enum Foo_Test { name, subMessage, notSet }.
Кроме того, он сгенерирует следующие методы:
-
Foo_Test whichTest()
: возвращает перечисление, указывающее, какое поле установлено. ВозвращаетFoo_Test.notSet
, если ни один из них не установлен. -
void clearTest()
: очищает значение поля oneof, которое установлено в данный момент (если оно есть), и устанавливает для случая oneof значениеFoo_Test.notSet
.
Для каждого поля внутри определения oneof генерируются обычные методы доступа к полям. Например, для name
:
-
String get name
: возвращает текущее значение поля, если в одном из случаевFoo_Test.name
. В противном случае возвращает значение по умолчанию. -
set name(String value)
: Устанавливает значение поля и устанавливает значение oneof дляFoo_Test.name
. После этогоget name
вернетvalue
, аwhichTest()
вернетFoo_Test.name
. -
void clearName()
: ничего не изменится, если в одном случае неFoo_Test.name
. В противном случае очищает значение поля. После этогоget name
вернет значение по умолчанию, аwhichTest()
вернетFoo_Test.notSet
.
Перечисления
Учитывая определение перечисления, например:
enum Color { RED = 0; GREEN = 1; BLUE = 2; }
Компилятор буфера протокола создаст класс с именем Color
, который расширяет класс ProtobufEnum
. Класс будет включать static const Color
для каждого из трех определенных значений, а также static const List<Color>
, содержащую все три значения. Он также будет включать следующий метод:
-
static Color valueOf(int value)
: возвращаетColor
, соответствующий заданному числовому значению.
Каждое значение будет иметь следующие свойства:
-
name
: имя перечисления, как указано в файле .proto. -
value
: целочисленное значение перечисления, как указано в файле .proto.
Обратите внимание, что язык .proto
позволяет нескольким символам перечисления иметь одно и то же числовое значение. Символы с одинаковым числовым значением являются синонимами. Например:
enum Foo { BAR = 0; BAZ = 0; }
В этом случае BAZ
является синонимом BAR
и будет определяться так:
static const Foo BAZ = BAR;.
Перечисление может быть определено вложенным в тип сообщения. Например, учитывая определение перечисления, например:
message Bar { enum Color { RED = 0; GREEN = 1; BLUE = 2; } }
Компилятор буфера протокола сгенерирует класс Bar
, который расширяет GeneratedMessage
, и класс Bar_Color
, который расширяет ProtobufEnum
.
Расширения (только proto2)
Учитывая файл foo_test.proto
, включающий сообщение с диапазоном расширений и определение расширения верхнего уровня:
message Foo { extensions 100 to 199; } extend Foo { optional int32 bar = 101; }
Компилятор буфера протокола сгенерирует в дополнение к классу Foo
класс Foo_test
, который будет содержать static Extension
для каждого поля расширения в файле вместе с методом для регистрации всех расширений в ExtensionRegistry
:
-
static final Extension bar
-
static void registerAllExtensions(ExtensionRegistry registry)
: регистрирует все определенные расширения в данном реестре.
Аксессоры расширения Foo
можно использовать следующим образом:
Foo foo = Foo(); foo.setExtension(Foo_test.bar, 1); assert(foo.hasExtension(Foo_test.bar)); assert(foo.getExtension(Foo_test.bar)) == 1);
Расширения также могут быть объявлены вложенными в другое сообщение:
message Baz { extend Foo { optional int32 bar = 124; } }.
В этом случае bar
расширения вместо этого объявляется как статический член класса Baz
.
При анализе сообщения, которое может иметь расширения, вы должны указать ExtensionRegistry
, в котором вы зарегистрировали все расширения, которые хотите анализировать. В противном случае эти расширения будут рассматриваться как неизвестные поля. Например:
ExtensionRegistry registry = ExtensionRegistry(); registry.add(Baz.bar); Foo foo = Foo.fromBuffer(input, registry);
Если у вас уже есть проанализированное сообщение с неизвестными полями, вы можете использовать reparseMessage
в ExtensionRegistry
для повторного анализа сообщения. Если набор неизвестных полей содержит расширения, присутствующие в реестре, эти расширения анализируются и удаляются из набора неизвестных полей. Расширения, уже присутствующие в сообщении, сохраняются.
Foo foo = Foo.fromBuffer(input); ExtensionRegistry registry = ExtensionRegistry(); registry.add(Baz.bar); Foo reparsed = registry.reparseMessage(foo);Имейте в виду, что этот метод получения расширений в целом более затратный. По возможности мы рекомендуем использовать
ExtensionRegistry
со всеми необходимыми расширениями при выполнении GeneratedMessage.fromBuffer
. Услуги
Учитывая определение службы:
service Foo { rpc Bar(FooRequest) returns(FooResponse); }
Компилятор буфера протокола можно вызвать с параметром `grpc` (например --dart_out=grpc:output_folder
), и в этом случае он сгенерирует код для поддержки gRPC . Дополнительные сведения см. в кратком руководстве по gRPC Dart .