Dart 程式碼產生
proto2 和 proto3 產生的程式碼之間的任何差異都會被突顯 - 請注意,這些差異在於本文檔中描述的產生的程式碼,而不是基礎 API,它們在兩個版本中是相同的。在閱讀本文檔之前,您應該閱讀 proto2 語言指南 和/或 proto3 語言指南。
編譯器調用
protocol buffer 編譯器需要一個 外掛程式來產生 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 {}
protocol buffer 編譯器會產生一個名為 Foo
的類別,它繼承了 GeneratedMessage
類別。
GeneratedMessage
類別定義了讓您可以檢查、操作、讀取或寫入整個訊息的方法。除了這些方法之外,Foo
類別還定義了以下方法和建構子
Foo()
:預設建構子。建立一個所有單數欄位都未設定且重複欄位為空的實例。Foo.fromBuffer(...)
:從表示訊息的序列化 protocol buffer 資料建立一個Foo
。Foo.fromJson(...)
:從編碼訊息的 JSON 字串建立一個Foo
。Foo clone()
:建立訊息中欄位的深層複製。Foo copyWith(void Function(Foo) updates)
:建立此訊息的可寫入副本,將updates
應用於它,並在傳回之前將副本標記為唯讀。static Foo create()
:建立單個Foo
的 Factory 函數。static PbList<Foo> createRepeated()
:建立一個 List 的 Factory 函數,該 List 實作了Foo
元素的可變重複欄位。static Foo getDefault()
:傳回Foo
的單例實例,它與新建構的 Foo 實例相同 (因此所有單數欄位都未設定,且所有重複欄位都為空)。
巢狀類型
訊息可以宣告在另一個訊息內部。例如
message Foo {
message Bar {
}
}
在這種情況下,編譯器會產生兩個類別:Foo
和 Foo_Bar
。
欄位
除了上一節中描述的方法之外,protocol buffer 編譯器還會為 .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;
}
protocol buffer 編譯器將產生一個名為 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;
}
}
protocol buffer 編譯器將產生一個名為 Bar
的類別 (它繼承了 GeneratedMessage
) 和一個名為 Bar_Color
的類別 (它繼承了 ProtobufEnum
)。
擴充 (僅限 proto2)
給定一個包含具有 擴充範圍 的訊息和頂層擴充定義的檔案 foo_test.proto
message Foo {
extensions 100 to 199;
}
extend Foo {
optional int32 bar = 101;
}
除了 Foo
類別之外,protocol buffer 編譯器還將產生一個 Foo_test
類別,它將包含檔案中每個擴充欄位的 static Extension
,以及一個用於在 ExtensionRegistry
中註冊所有擴充的方法
static final Extension bar
static void registerAllExtensions(ExtensionRegistry registry)
:在給定的 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
來重新剖析訊息。如果未知欄位集合包含 registry 中存在的擴充,則會剖析這些擴充並從未知欄位集合中移除。訊息中已存在的擴充會被保留。
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
) 調用 protocol buffer 編譯器,在這種情況下,它將產生程式碼來支援 gRPC。 請參閱 gRPC Dart 快速入門指南 以取得更多詳細資訊。