Dart 生成的代码

描述协议缓冲区编译器为任何给定的协议定义生成的 Dart 代码。

突出显示了 proto2 和 proto3 生成的代码之间的任何差异 - 请注意,这些差异存在于本文档中描述的生成的代码中,而不是基本 API 中,这两个版本中的基本 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 路径(使用 --proto_path-I 命令行标志指定)将替换为输出路径(使用 --dart_out 标志指定)。

例如,当您按如下方式调用编译器时

protoc --proto_path=src --dart_out=build/gen src/foo.proto src/bar/baz.proto

编译器将读取文件 src/foo.protosrc/bar/baz.proto。它会生成:build/gen/foo.pb.dartbuild/gen/bar/baz.pb.dart。编译器会在必要时自动创建目录 build/gen/bar,但它不会创建 buildbuild/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():工厂函数,用于创建一个实现 Foo 元素的可变重复字段的列表。
  • static Foo getDefault():返回 Foo 的单例实例,它与新构造的 Foo 实例相同(因此所有单数字段都未设置,所有重复字段都为空)。

嵌套类型

可以在另一条消息中声明一条消息。例如

message Foo {
  message Bar {
  }
}

在这种情况下,编译器将生成两个类:FooFoo_Bar

字段

除了上一节中描述的方法之外,协议缓冲区编译器还为 .proto 文件中消息内定义的每个字段生成访问器方法。

请注意,生成的名称始终使用驼峰式命名法,即使 .proto 文件中的字段名称使用带下划线的 lower-case(应该如此)。大小写转换的工作原理如下

  1. 对于名称中的每个下划线,都会删除下划线,并将后面的字母大写。
  2. 如果名称将附加前缀(例如“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() 将返回 trueget foo 将返回 value
  • void clearFoo():清除字段的值。调用此方法后,hasFoo() 将返回 falseget foo 将返回默认值。

对于其他简单字段类型,将根据标量值类型表选择相应的 Dart 类型。对于消息和枚举类型,值类型将替换为消息或枚举类。

单数原始字段(proto3)

对于此字段定义

int32 foo = 1;

编译器将在消息类中生成以下访问器方法

  • int get foo:返回字段的当前值。如果未设置字段,则返回默认值。
  • set foo(int value):设置字段的值。调用此方法后,get foo 将返回 value
  • void clearFoo():清除字段的值。调用此方法后,get foo 将返回默认值。

注意:由于 Dart proto3 实现中的一个怪癖,即使 proto 定义中没有使用 optional 修饰符来请求存在语义,也会生成以下方法。

  • bool hasFoo():如果设置了字段,则返回 true
  • void clearFoo():清除字段的值。调用此方法后,hasFoo() 将返回 falseget 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() 将返回 trueget bar 将返回 value
  • bool hasBar():如果设置了字段,则返回 true
  • void clearBar():清除字段的值。调用此方法后,hasBar() 将返回 falseget 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<int32, int32> map_field = 1;

编译器将生成以下 getter

  • Map<int, int> get mapField:返回支持字段的 Dart 映射。如果未设置字段,则返回空映射。对映射的修改会反映在字段中。

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 案例设置为 Foo_Test.notSet

对于 oneof 定义中的每个字段,都会生成常规字段访问器方法。例如,对于 name

  • String get name:如果 oneof 案例为 Foo_Test.name,则返回字段的当前值。否则,返回默认值。
  • set name(String value):设置字段的值并将 oneof 案例设置为 Foo_Test.name。调用此方法后,get name 将返回 valuewhichTest() 将返回 Foo_Test.name
  • void clearName():如果 oneof 案例不是 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;
}

在这种情况下,BAZBAR 的同义词,并将这样定义

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 快速入门指南