Dart 生成的程式碼
proto2、proto3 和 Editions 生成的程式碼之間的任何差異都會被突出顯示——請注意,這些差異如本文件所述是在生成的程式碼中,而不是在基礎 API 中,基礎 API 在這兩個版本中是相同的。在閱讀本文件之前,您應該閱讀proto2 語言指南、proto3 語言指南或Editions 語言指南。
編譯器呼叫
協議緩衝區編譯器需要一個外掛來生成 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.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():用於建立實現Foo元素的可變重複欄位的 List 的工廠函式。static Foo getDefault():返回Foo的單例例項,該例項與新構造的 Foo 例項相同(因此所有單一欄位都未設定,所有重複欄位都為空)。
巢狀型別
訊息可以宣告在另一個訊息內部。例如:
message Foo {
message Bar {
}
}
在這種情況下,編譯器生成兩個類:Foo 和 Foo_Bar。
欄位
除了上一節中描述的方法之外,協議緩衝區編譯器還會為 .proto 檔案中訊息內定義的每個欄位生成訪問器方法。
請注意,生成的名稱始終使用駝峰命名法,即使 .proto 檔案中的欄位名稱使用帶下劃線的小寫字母(它應該如此)。大小寫轉換工作方式如下
- 對於名稱中的每個下劃線,下劃線被移除,其後的字母大寫。
- 如果名稱將帶有字首(例如“has”),則首字母大寫。否則,小寫。
因此,對於欄位 foo_bar_baz,getter 變為 get fooBarBaz,帶有 has 字首的方法將是 hasFooBarBaz。
單一原始欄位
所有欄位在 Dart 實現中都具有顯式存在。
對於以下欄位定義
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 實現中的一個怪癖,即使配置了隱式存在,也會生成以下方法。bool hasFoo():如果欄位已設定,則返回true。注意
如果 proto 是用支援隱式存在的另一種語言(例如 Java)序列化的,則此值實際上不可信。儘管 Dart 跟蹤存在,但其他語言不跟蹤,並且往返於零值隱式存在欄位將使其從 Dart 的角度“消失”。void clearFoo():清除欄位的值。呼叫此方法後,hasFoo()將返回false,get foo將返回預設值。
對於其他簡單欄位型別,根據標量值型別表選擇相應的 Dart 型別。對於訊息和列舉型別,值型別被替換為訊息或列舉類。
奇異訊息欄位
給定訊息型別:
message Bar {}
對於帶有 Bar 欄位的訊息
// proto2
message Baz {
optional Bar bar = 1;
// The generated code is the same result if required instead of optional.
}
// proto3 and editions
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 欄位
對於此欄位定義:
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 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 情況設定為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 情況不是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。
擴充套件 (proto3 中不可用)
給定一個 foo_test.proto 檔案,其中包括一個帶有擴充套件範圍的訊息和一個頂層擴充套件定義
message Foo {
extensions 100 to 199;
}
extend Foo {
optional int32 bar = 101;
}
協議緩衝區編譯器除了 Foo 類之外,還將生成一個 Foo_test 類,該類將包含檔案中每個擴充套件欄位的 static Extension,以及一個用於在 ExtensionRegistry 中註冊所有擴充套件的方法
static final Extension barstatic 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 {
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。
服務(Services)
給定一個服務定義:
service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}
協議緩衝區編譯器可以使用 `grpc` 選項呼叫(例如 --dart_out=grpc:output_folder),在這種情況下,它將生成支援 gRPC 的程式碼。有關更多詳細資訊,請參閱 gRPC Dart 快速入門指南。