[ProtoBuf翻译]Dart生成的代码

2,277 阅读10分钟

原文地址:developers.google.com/protocol-bu…

原文作者:

发布时间:

本页描述了协议缓冲编译器为任何给定的协议定义生成的Dart代码。proto2和proto3生成的代码之间的任何差异都会被高亮显示--请注意,这些差异是在本文档中描述的生成的代码中,而不是在基础API中,这两个版本的代码是相同的。在阅读本文档之前,你应该阅读proto2语言指南和/或proto3语言指南

编译器调用

协议缓冲区编译器需要一个插件来生成Dart代码。按照说明安装它,会提供一个protoc-gen-dart二进制文件,protoc在使用--dart_out命令行标志调用时使用。--dart_out标志告诉编译器在哪里写入Dart源文件。对于一个.proto文件的输入,编译器会产生一个.bb.dart文件。

.pb.dart文件的名称是通过对.proto文件的名称进行两点修改来计算的。

  • 扩展名(.proto)被替换为.pb.dart. 例如,一个名为foo.proto的文件会产生一个名为foo.bb.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.bb.dart和build/gen/bar/baz.bb.dart。如果需要的话,编译器会自动创建build/gen/bar目录,但它不会创建build或build/gen,它们必须已经存在。

消息

给出一个简单的消息声明。

message Foo {}

协议缓冲区编译器生成了一个名为Foo的类,它扩展了类GeneratedMessage

类GeneratedMessage定义了让你检查、操作、读取或写入整个消息的方法。除了这些方法,Foo类还定义了以下方法和构造器。

  • Foo(): 默认构造函数. 创建一个所有单数字段都未设置,重复字段为空的实例。
  • Foo.fromBuffer(...):默认构造函数。从代表消息的序列化协议缓冲区数据中创建一个Foo。
  • Foo.fromJson(...): 从代表消息的序列化协议缓冲区数据创建一个Foo。从JSON字符串中创建一个Foo,对消息进行编码。
  • Foo.clone():从编码消息的JSON字符串中创建一个Foo。创建消息中字段的深度克隆。
  • Foo copyWith(void Function(Foo) updates)。为这条消息创建一个可写的副本,对其进行更新,并在返回之前将副本标记为只读。
  • static Foo create()。用于创建单个Foo的工厂函数。
  • static PbList<Foo> createRepeated()。工厂函数,用于创建一个List,实现Foo元素的可变重复域。
  • 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将返回值。
  • 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将返回值。
  • bool hasBar():设置字段的值。如果该字段被设置,则返回true。
  • void clearBar():清除字段的值。清空字段的值。调用此函数后,hasBar()将返回false,get bar将返回默认值。
  • Bar ensureBar():将bar设置为空实例。如果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字段这样。

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  google.protobuf.Any details = 2;
}

在我们生成的代码中,细节字段的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的定义。

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().如果没有设置,则返回Foo_Test.notSet。清除当前被设置的oneof字段的值(如果有的话),并将oneof情况设置为Foo_Test.notSet。

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

  • String get name: 如果oneof情况是Foo_Test.name, 则返回该字段的当前值. 否则,返回默认值。
  • set name(String value)。设置字段的值,并将oneof大小写设置为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 {
  RED = 0;
  GREEN = 1;
  BLUE = 2;
}

协议缓冲区编译器将生成一个名为Color的类,它扩展了ProtobufEnum类。该类将为定义的三个值中的每一个定义一个静态的Const Color,以及一个包含所有三个值的静态的List<Color>。它还将包含以下方法。

  • static Color valueOf(int value): 返回给定数值对应的Color。

每个值都会有以下属性。

  • name:枚举的名称,在.proto文件中指定。
  • value: enum的整数值,在.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;
  }
}

在这种情况下,扩展条被声明为Baz类的一个静态成员。

当解析一个可能有扩展名的消息时,你必须提供一个扩展名注册表(ExtensionRegistry),在这个注册表中,你必须注册任何你希望能够解析的扩展名。否则,这些扩展将被当作未知字段处理。例如

ExtensionRegistry registry = ExtensionRegistry.EMPTY;
registry.add(Baz.bar);
Foo foo = Foo.fromBuffer(input, registry);

如果你已经有了一个包含未知字段的解析消息,你可以在一个ExtensionRegistry上使用reparseMessage来解析消息。如果未知字段集包含注册表中存在的扩展名,这些扩展名将被解析并从未知字段集中删除。已经存在于消息中的扩展名将被保留。

Foo foo = Foo.fromBuffer(input);
ExtensionRegistry registry = ExtensionRegistry.EMPTY;
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快速入门指南


www.deepl.com 翻译