Dart 生成代码
proto2 和 proto3 生成代码之间的任何差异都会突出显示 - 请注意,这些差异在于本文档中描述的生成代码,而不是基本 API,它们在两个版本中是相同的。在阅读本文档之前,您应该阅读 proto2 语言指南 和/或 proto3 语言指南。
编译器调用
协议缓冲区编译器需要一个 插件来生成 Dart 代码。按照 说明 安装它会提供一个 protoc-gen-dart
二进制文件,当使用 --dart_out
命令行标志调用 protoc
时,protoc
会使用它。--dart_out
标志告诉编译器将 Dart 源文件写入到哪里。对于 .proto
文件输入,编译器会生成 .pb.dart
文件等。
.pb.dart
文件的名称是通过获取 .proto
文件的名称并进行两项更改来计算的
- 扩展名 (
.proto
) 被替换为.pb.dart
。例如,名为foo.proto
的文件会生成名为foo.pb.dart
的输出文件。 - proto 路径(使用
--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(...)
:从编码消息的 JSON 字符串创建Foo
。Foo clone()
:创建消息中字段的深层克隆。Foo copyWith(void Function(Foo) updates)
:创建此消息的可写副本,将updates
应用于它,并在返回之前将副本标记为只读。static Foo create()
:用于创建单个Foo
的工厂函数。static PbList<Foo> createRepeated()
:用于创建 List 的工厂函数,该 List 实现Foo
元素的 mutable repeated field。static Foo getDefault()
:返回Foo
的单例实例,它与新构造的 Foo 实例相同(因此所有单数字段都未设置,所有重复字段都为空)。
嵌套类型
消息可以在另一个消息内部声明。例如
message Foo {
message Bar {
}
}
在这种情况下,编译器会生成两个类:Foo
和 Foo_Bar
。
字段
除了上一节中描述的方法之外,协议缓冲区编译器还会为 .proto
文件中消息内定义的每个字段生成访问器方法。
请注意,即使 .proto
文件中的字段名称使用带下划线的小写字母(正如应该的那样),生成的名称始终使用驼峰式命名。大小写转换的工作原理如下
- 对于名称中的每个下划线,下划线将被删除,并且后面的字母将大写。
- 如果名称将附加前缀(例如 “has”),则第一个字母将大写。否则,它将小写。
因此,对于字段 foo_bar_baz
,getter 变为 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
将返回默认值。bool hasFoo()
:如果设置了字段,则返回true
。void clearFoo()
:清除字段的值。调用此方法后,hasFoo()
将返回false
,而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()
:如果hasBar()
返回false
,则将bar
设置为空实例,然后返回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
字段定义,如下所示
map<int32, int32> map_field = 1;
编译器将生成以下 getter
Map<int, int> get mapField
:返回支持该字段的 Dart map。如果未设置字段,则返回空 map。对 map 的修改会反映在字段中。
Any
给定一个 Any
字段,如下所示
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
google.protobuf.Any details = 2;
}
在我们的生成代码中,details
字段的 getter 返回 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
给定一个 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 case 设置为Foo_Test.notSet
。
对于 oneof 定义中的每个字段,都会生成常规字段访问器方法。例如,对于 name
String get name
:如果 oneof case 为Foo_Test.name
,则返回字段的当前值。否则,返回默认值。set name(String value)
:设置字段的值,并将 oneof case 设置为Foo_Test.name
。调用此方法后,get name
将返回value
,而whichTest()
将返回Foo_Test.name
。void clearName()
:如果 oneof case 不是Foo_Test.name
,则不会更改任何内容。否则,清除字段的值。调用此方法后,get name
将返回默认值,而whichTest()
将返回Foo_Test.notSet
。
枚举
给定一个枚举定义,如下所示
enum Color {
COLOR_UNSPECIFIED = 0;
COLOR_RED = 1;
COLOR_GREEN = 2;
COLOR_BLUE = 3;
}
协议缓冲区编译器将生成一个名为 Color
的类,它继承自 ProtobufEnum
类。该类将为四个值中的每一个值都包含一个 static const Color
,以及一个包含这些值的 static const List<Color>
。
static const List<Color> values = <Color> [
COLOR_UNSPECIFIED,
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE,
];
它还将包含以下方法
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 {
COLOR_UNSPECIFIED = 0;
COLOR_RED = 1;
COLOR_GREEN = 2;
COLOR_BLUE = 3;
}
}
协议缓冲区编译器将生成一个名为 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);
如果您已经有一个带有未知字段的已解析消息,则可以在 ExtensionRegistry
上使用 reparseMessage
来重新解析消息。如果未知字段集中包含注册表中存在的扩展,则会解析这些扩展并从未知字段集中删除。消息中已存在的扩展将被保留。
Foo foo = Foo.fromBuffer(input);
ExtensionRegistry registry = ExtensionRegistry();
registry.add(Baz.bar);
Foo reparsed = registry.reparseMessage(foo);
请注意,这种检索扩展的方法总体上更昂贵。如果可能,我们建议在使用 GeneratedMessage.fromBuffer
时将 ExtensionRegistry
与所有需要的扩展一起使用。
服务
给定一个服务定义
service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}
可以使用 grpc
选项(例如 --dart_out=grpc:output_folder
)调用协议缓冲区编译器,在这种情况下,它将生成代码以支持 gRPC。有关更多详细信息,请参阅 gRPC Dart 快速入门指南。