Auf dieser Seite wird beschrieben, welcher Dart-Code vom Protokollpuffer-Compiler für eine bestimmte Protokolldefinition generiert wird. Unterschiede zwischen dem von proto2 und proto3 generierten Code werden hervorgehoben. Beachten Sie, dass diese Unterschiede im generierten Code auftreten, wie in diesem Dokument beschrieben, und nicht in der Basis-API, die in beiden Versionen gleich sind. Bevor Sie dieses Dokument lesen, sollten Sie den Leitfaden zur Sprache proto2 und/oder den Leitfaden zur Sprache Proto3 lesen.
Compiler-Aufruf
Der Protokollpuffer-Compiler erfordert ein Plug-in, um Dart-Code zu generieren.
Bei der Installation gemäß der Anleitung wird ein protoc-gen-dart
-Binärprogramm bereitgestellt, das protoc
verwendet, wenn es mit dem --dart_out
-Befehlszeilen-Flag aufgerufen wird. Das Flag --dart_out
teilt dem Compiler mit, wohin die Dart-Quelldateien geschrieben werden. Bei einer .proto
-Dateieingabe erzeugt der Compiler unter anderem eine .pb.dart
-Datei.
Der Name der Datei .pb.dart
wird berechnet, indem der Name der Datei .proto
verwendet und zwei Änderungen vorgenommen werden:
-
Die Erweiterung (
.proto
) wurde durch.pb.dart
ersetzt. Eine Datei namensfoo.proto
führt beispielsweise zu einer Ausgabedatei namensfoo.pb.dart
. -
Der Proto-Pfad (mit dem Befehlszeilen-Flag
--proto_path
oder-I
angegeben) wird durch den Ausgabepfad ersetzt, der mit dem Flag--dart_out
angegeben wird.
Wenn Sie beispielsweise den Compiler so aufrufen:
protoc --proto_path=src --dart_out=build/gen src/foo.proto src/bar/baz.proto
Der Compiler liest die Dateien src/foo.proto
und src/bar/baz.proto
. Sie erzeugt Folgendes: build/gen/foo.pb.dart
und build/gen/bar/baz.pb.dart
.
Der Compiler erstellt gegebenenfalls automatisch das Verzeichnis build/gen/bar
, erstellt aber nicht build
oder build/gen
. Diese müssen bereits vorhanden sein.
Nachrichten
Einfache Erklärung:
message Foo {}
Der Protokollpuffer-Compiler generiert eine Klasse mit dem Namen Foo
, die die Klasse
GeneratedMessage
erweitert.
Die Klasse GeneratedMessage
definiert Methoden, mit denen Sie die gesamte Nachricht prüfen, bearbeiten, lesen oder schreiben können. Zusätzlich zu diesen Methoden definiert die Klasse Foo
die folgenden Methoden und Konstruktoren:
Foo()
: Standardkonstruktor. Erstellt eine Instanz, in der alle einzelnen Felder nicht festgelegt und wiederkehrende Felder leer sind.Foo.fromBuffer(...)
: Erstellt eineFoo
aus serialisierten Protokollpufferdaten, die die Nachricht darstellen.Foo.fromJson(...)
: Erstellt einFoo
aus einem JSON-String, der die Nachricht codiert.Foo clone()
: Erstellt einen tiefen Klon der Felder in der Nachricht.Foo copyWith(void Function(Foo) updates)
: Erstellt eine beschreibbare Kopie dieser Nachricht, wendet dasupdates
darauf an und markiert die Kopie vor dem Zurückgeben als schreibgeschützt.static Foo create()
: Factory-Funktion zum Erstellen eines einzelnenFoo
.static PbList<Foo> createRepeated()
: Factory-Funktion zum Erstellen einer Liste, die ein änderbares wiederkehrendes Feld vonFoo
-Elementen implementiert.static Foo getDefault()
: Gibt eine Singleton-Instanz vonFoo
zurück. Diese Instanz ist mit einer neu erstellten Foo-Instanz identisch. Daher sind alle einzelnen Felder nicht festgelegt und alle wiederkehrenden Felder sind leer.
Verschachtelte Typen
Eine Nachricht kann in einer anderen Nachricht deklariert werden. Beispiel:
message Foo { message Bar { } }
In diesem Fall generiert der Compiler zwei Klassen: Foo
und Foo_Bar
.
Felder
Zusätzlich zu den im vorherigen Abschnitt beschriebenen Methoden generiert der Protokollpufferkompilierer Zugriffsmethoden für jedes Feld, das in der Nachricht in der Datei .proto
definiert ist.
Beachten Sie, dass die generierten Namen immer die Camel-Case-Namenskonvention verwenden, auch wenn im Feldnamen in der Datei .proto
Kleinbuchstaben (wie empfohlen) verwendet werden. Die Umwandlung von Groß- und Kleinschreibung funktioniert folgendermaßen:
- Für jeden Unterstrich im Namen wird der Unterstrich entfernt und der folgende Buchstabe großgeschrieben.
- Wenn an den Namen ein Präfix angehängt ist (z.B. „hat“), wird der erste Buchstabe großgeschrieben. Andernfalls wird die Kleinschreibung verwendet.
Für das Feld foo_bar_baz
wird also der Getter get fooBarBaz
und eine Methode mit dem Präfix has
wäre hasFooBarBaz
.
Einzelne Primitive Fields (proto2)
Für jede dieser Felddefinitionen gilt:
optional int32 foo = 1; required int32 foo = 1;
Der Compiler generiert die folgenden Zugriffsmethoden in der Nachrichtenklasse:
int get foo
: Gibt den aktuellen Wert des Felds zurück. Wenn das Feld nicht festgelegt ist, wird der Standardwert zurückgegeben.bool hasFoo()
: Gibttrue
zurück, wenn das Feld festgelegt ist.set foo(int value)
: Legt den Wert des Felds fest. Nach dem Aufruf gibthasFoo()
true
zurück undget foo
gibtvalue
zurück.void clearFoo()
: Löscht den Wert des Felds. Nach dem Aufruf gibthasFoo()
den Wertfalse
undget foo
den Standardwert zurück.
Für andere einfache Feldtypen wird der entsprechende Dart-Typ entsprechend der Tabelle mit skalaren Werttypen ausgewählt. Bei Nachrichten- und Enum-Typen wird der Werttyp durch die Nachrichten- oder Enum-Klasse ersetzt.
Einzelne Primitive Fields (proto3)
Für diese Felddefinition:
int32 foo = 1;
Der Compiler generiert die folgenden Zugriffsmethoden in der Nachrichtenklasse:
int get foo
: Gibt den aktuellen Wert des Felds zurück. Wenn das Feld nicht festgelegt ist, wird der Standardwert zurückgegeben.set foo(int value)
: Legt den Wert des Felds fest. Nach dem Aufruf gibtget foo
value
zurück.void clearFoo()
: Löscht den Wert des Felds. Nach dem Aufruf gibtget foo
den Standardwert zurück.
Einzelne Nachrichtenfelder
Für den Nachrichtentyp gilt Folgendes:
message Bar {}Für eine Nachricht mit dem Feld
Bar
gilt:
// 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; }
Der Compiler generiert die folgenden Zugriffsmethoden in der Nachrichtenklasse:
Bar get bar
: Gibt den aktuellen Wert des Felds zurück. Wenn das Feld nicht festgelegt ist, wird der Standardwert zurückgegeben.set bar(Bar value)
: Legt den Wert des Felds fest. Nach dem Aufruf gibthasBar()
true
zurück undget bar
gibtvalue
zurück.bool hasBar()
: Gibttrue
zurück, wenn das Feld festgelegt ist.void clearBar()
: Löscht den Wert des Felds. Nach dem Aufruf gibthasBar()
den Wertfalse
undget bar
den Standardwert zurück.Bar ensureBar()
: Setztbar
auf die leere Instanz, wennhasBar()
false
zurückgibt, und dann den Wert vonbar
. Nach dem Aufruf gibthasBar()
true
zurück.
Wiederkehrende Felder
Für diese Felddefinition:
repeated int32 foo = 1;
Der Compiler generiert Folgendes:
List<int> get foo
: Gibt die Liste zurück, die das Feld unterstützt. Wenn das Feld nicht festgelegt ist, wird eine leere Liste zurückgegeben. Änderungen an der Liste werden im Feld angezeigt.
Int64-Felder
Für diese Felddefinition:
// proto2 optional int64 bar = 1; // proto3 int64 bar = 1;
Der Compiler generiert Folgendes:
Int64 get bar
: Gibt einInt64
-Objekt zurück, das den Feldwert enthält.
Beachten Sie, dass Int64
nicht in die Dart Core-Bibliotheken eingebunden ist. Für die Arbeit mit diesen Objekten müssen Sie möglicherweise die Dart-Bibliothek fixnum
importieren:
import 'package:fixnum/fixnum.dart';
Kartenfelder
Bei einer map
-Felddefinition wie dieser:
map<int32, int32> map_field = 1;
Der Compiler generiert den folgenden Getter:
Map<int, int> get mapField
: Gibt die Dart-Karte zurück, die das Feld unterstützt. Wenn das Feld nicht festgelegt ist, wird eine leere Karte zurückgegeben. Änderungen an der Karte werden im Feld angezeigt.
Alle
Beispiel für ein Any
-Feld:
import "google/protobuf/any.proto"; message ErrorStatus { string message = 1; google.protobuf.Any details = 2; }
In unserem generierten Code gibt der Getter für das Feld details
eine Instanz von com.google.protobuf.Any
zurück. Dies bietet die folgenden speziellen Methoden zum Verpacken und Entpacken der Werte von 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'});
Einer
Bei einer oneof
-Definition wie dieser:
message Foo { oneof test { string name = 1; SubMessage sub_message = 2; } }
Der Compiler generiert den folgenden Dart-Enum-Typ:
enum Foo_Test { name, subMessage, notSet }
Außerdem werden die folgenden Methoden generiert:
Foo_Test whichTest()
: Gibt das Enum zurück, das angibt, welches Feld festgelegt ist. GibtFoo_Test.notSet
zurück, wenn keine festgelegt ist.void clearTest()
: Löscht den Wert des einmaligen Felds, das derzeit festgelegt ist (falls vorhanden) und legt den Wert für den WertFoo_Test.notSet
fest.
Für jedes Feld innerhalb der Definition werden die regulären Zugriffsmethoden für Felder generiert. Beispiel für name
:
String get name
: Gibt den aktuellen Wert des Felds zurück, wenn der WertFoo_Test.name
ist. Andernfalls wird der Standardwert zurückgegeben.set name(String value)
: Legt den Wert des Felds fest und legt den Wert für „Oneof Case“ aufFoo_Test.name
fest. Nach dem Aufruf gibtget name
value
zurück undwhichTest()
gibtFoo_Test.name
zurück.void clearName()
: Wenn der Fall nichtFoo_Test.name
ist, ändert sich nichts. Andernfalls wird der Wert des Felds gelöscht. Nach dem Aufruf gibtget name
den Standardwert undwhichTest()
den WertFoo_Test.notSet
zurück.
Aufzählungen
Bei einer Enum-Definition wie
enum Color { RED = 0; GREEN = 1; BLUE = 2; }
Der Protokollpuffer-Compiler generiert eine Klasse mit dem Namen Color
, die die Klasse ProtobufEnum
erweitert.
Die Klasse enthält eine static const Color
für jeden der drei definierten Werte sowie eine static const List<Color>
mit allen drei Werten. Sie enthält außerdem die folgende Methode:
static Color valueOf(int value)
: Gibt dieColor
des entsprechenden numerischen Werts zurück.
Jeder Wert hat die folgenden Eigenschaften:
name
: Der Name des Enum, wie in der .proto-Datei angegeben.value
: Der Ganzzahlwert der Aufzählung, wie in der .proto-Datei angegeben.
In der Sprache .proto
können mehrere Enum-Symbole denselben numerischen Wert haben.
Symbole mit demselben numerischen Wert sind Synonyme. Beispiel:
enum Foo { BAR = 0; BAZ = 0; }
In diesem Fall ist BAZ
ein Synonym für BAR
und wird so definiert:
static const Foo BAZ = BAR;
Ein Enum kann in einem Nachrichtentyp verschachtelt definiert werden. Beispiel: Bei einer Enum-Definition wie
message Bar { enum Color { RED = 0; GREEN = 1; BLUE = 2; } }
Der Protokollpuffer-Compiler generiert eine Klasse namens Bar
, die
GeneratedMessage
erweitert, und eine Klasse namens Bar_Color
, die ProtobufEnum
erweitert.
Erweiterungen (nur proto2)
Bei einer Datei foo_test.proto
mit einer Nachricht mit einem Erweiterungsbereich und einer Definition der obersten Ebene:
message Foo { extensions 100 to 199; } extend Foo { optional int32 bar = 101; }
Der Protokollpuffer-Compiler generiert zusätzlich zur Klasse Foo
eine Klasse Foo_test
, die ein static Extension
für jedes Erweiterungsfeld in der Datei sowie eine Methode zum Registrieren aller Tests in einer ExtensionRegistry
enthält:
static final Extension bar
static void registerAllExtensions(ExtensionRegistry registry)
: Registriert alle definierten Erweiterungen in der angegebenen Registry.
Die Zugriffsfunktion für Foo
von Erweiterungen kann so verwendet werden:
Foo foo = Foo(); foo.setExtension(Foo_test.bar, 1); assert(foo.hasExtension(Foo_test.bar)); assert(foo.getExtension(Foo_test.bar)) == 1);
Erweiterungen können auch in einer anderen Nachricht verschachtelt sein:
message Baz { extend Foo { optional int32 bar = 124; } }
In diesem Fall wird die Erweiterung bar
stattdessen als statisches Mitglied der Klasse Baz
deklariert .
Beim Parsen einer Nachricht, die möglicherweise Erweiterungen enthält, müssen Sie eine ExtensionRegistry
angeben, in der Sie alle Erweiterungen registriert haben, die geparst werden sollen. Andernfalls werden diese Erweiterungen wie unbekannte Felder behandelt. Beispiel:
ExtensionRegistry registry = ExtensionRegistry(); registry.add(Baz.bar); Foo foo = Foo.fromBuffer(input, registry);
Wenn Sie bereits eine geparste Nachricht mit unbekannten Feldern haben, können Sie die Nachricht mit reparseMessage
für ExtensionRegistry
neu parsen. Wenn die Reihe unbekannter Felder Erweiterungen enthält, die in der Registry vorhanden sind, werden diese Erweiterungen geparst und aus der unbekannten Feldgruppe entfernt. Bereits in der Nachricht vorhandene Erweiterungen werden beibehalten.
Foo foo = Foo.fromBuffer(input); ExtensionRegistry registry = ExtensionRegistry(); registry.add(Baz.bar); Foo reparsed = registry.reparseMessage(foo);Diese Methode zum Abrufen von Erweiterungen ist insgesamt teurer. Nach Möglichkeit empfehlen wir, bei allen erforderlichen Erweiterungen
ExtensionRegistry
mit GeneratedMessage.fromBuffer
zu verwenden.
Dienste
Mit einer Dienstdefinition:
service Foo { rpc Bar(FooRequest) returns(FooResponse); }
Der Protokollpuffer-Compiler kann mit der Option „grpc“ aufgerufen werden (z. B. --dart_out=grpc:output_folder
). In diesem Fall generiert er Code zur Unterstützung von gRPC. Weitere Informationen finden Sie in der Kurzanleitung für gRPC Dart.