Sprachführer (proto 3)
Diese Anleitung beschreibt, wie die Protocol Buffer-Sprache verwendet wird, um Ihre Protocol Buffer-Daten zu strukturieren, einschließlich der Syntax von .proto-Dateien und wie Sie Datenzugriffsklassen aus Ihren .proto-Dateien generieren. Sie behandelt die proto3-Revision der Protocol Buffers-Sprache.
Informationen zur Editions-Syntax finden Sie im Protobuf Editions Sprachleitfaden.
Informationen zur proto2-Syntax finden Sie im Proto2 Sprachleitfaden.
Dies ist ein Referenzhandbuch – ein Schritt-für-Schritt-Beispiel, das viele der in diesem Dokument beschriebenen Funktionen verwendet, finden Sie im Tutorial für Ihre gewählte Sprache.
Definieren eines Nachrichtentyps
Betrachten wir zunächst ein sehr einfaches Beispiel. Angenommen, Sie möchten ein Nachrichtenformat für Suchanfragen definieren, bei dem jede Suchanfrage eine Abfragestrzeichenkette, die gewünschte Ergebnisseite und die Anzahl der Ergebnisse pro Seite enthält. Hier ist die .proto-Datei, die Sie zur Definition des Nachrichtentyps verwenden.
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
Die erste Zeile der Datei gibt an, dass Sie die proto3-Revision der Protobuf-Sprachspezifikation verwenden.
- Die
edition(odersyntaxfür proto2/proto3) muss die erste nicht leere, nicht auskommentierte Zeile der Datei sein. - Wenn keine
editionodersyntaxangegeben ist, geht der Protocol Buffer-Compiler davon aus, dass Sie proto2 verwenden.
- Die
Die
SearchRequest-Nachrichtendefinition gibt drei Felder (Namens-/Wertpaare) an, eines für jedes Datenelement, das Sie in diesem Nachrichtentyp aufnehmen möchten. Jedes Feld hat einen Namen und einen Typ.
Festlegen von Feldtypen
Im vorherigen Beispiel sind alle Felder skalare Typen: zwei ganze Zahlen (page_number und results_per_page) und eine Zeichenkette (query). Sie können auch Aufzählungen und zusammengesetzte Typen wie andere Nachrichtentypen für Ihr Feld angeben.
Zuweisen von Feldnummern
Sie müssen jedem Feld in Ihrer Nachrichtendefinition eine Nummer zwischen 1 und 536.870.911 zuweisen, mit den folgenden Einschränkungen:
- Die angegebene Nummer muss eindeutig für alle Felder dieser Nachricht sein.
- Feldnummern
19.000bis19.999sind für die Protocol Buffers-Implementierung reserviert. Der Protocol Buffer-Compiler wird sich beschweren, wenn Sie eine dieser reservierten Feldnummern in Ihrer Nachricht verwenden. - Sie dürfen keine zuvor reservierten Feldnummern oder Feldnummern verwenden, die für Erweiterungen zugewiesen wurden.
Diese Nummer kann nicht geändert werden, sobald Ihr Nachrichtentyp in Gebrauch ist, da sie das Feld im Nachrichten-Wire-Format identifiziert. Eine „Änderung“ einer Feldnummer entspricht dem Löschen dieses Feldes und der Erstellung eines neuen Feldes mit demselben Typ, aber einer neuen Nummer. Weitere Informationen hierzu finden Sie unter Felder löschen.
Feldnummern dürfen niemals wiederverwendet werden. Nehmen Sie niemals eine Feldnummer aus der reservierten Liste zur Wiederverwendung mit einer neuen Felddefinition auf. Siehe Folgen der Wiederverwendung von Feldnummern.
Sie sollten die Feldnummern 1 bis 15 für die am häufigsten gesetzten Felder verwenden. Kleinere Feldnummern benötigen weniger Speicherplatz im Wire-Format. Feldnummern im Bereich 1 bis 15 benötigen beispielsweise ein Byte zur Kodierung. Feldnummern im Bereich 16 bis 2047 benötigen zwei Bytes. Mehr dazu erfahren Sie in Protocol Buffer-Kodierung.
Folgen der Wiederverwendung von Feldnummern
Die Wiederverwendung einer Feldnummer macht die Dekodierung von Wire-Format-Nachrichten mehrdeutig.
Das Protobuf-Wire-Format ist schlank und bietet keine Möglichkeit, Felder zu erkennen, die mit einer Definition kodiert und mit einer anderen dekodiert wurden.
Das Kodieren eines Feldes mit einer Definition und anschließende Dekodieren desselben Feldes mit einer anderen Definition kann zu folgenden Problemen führen:
- Verlorene Entwicklerzeit durch Debugging
- Ein Parse-/Merge-Fehler (im besten Fall)
- Ausgelaufene PII/SPII (personenbezogene/sensible personenbezogene Daten)
- Datenbeschädigung
Häufige Ursachen für die Wiederverwendung von Feldnummern
- Neubewertung von Feldern (manchmal zur Erreichung einer ästhetisch ansprechenderen Reihenfolge für Felder). Neubewertung löscht und fügt effektiv alle beteiligten Felder neu hinzu, was zu inkompatiblen Wire-Format-Änderungen führt.
- Löschen eines Feldes und Nicht-Reservieren der Nummer, um zukünftige Wiederverwendung zu verhindern.
Die Feldnummer ist auf 29 Bits statt 32 Bits begrenzt, da drei Bits zur Angabe des Wire-Formats des Feldes verwendet werden. Mehr dazu finden Sie im Thema Kodierung.
Festlegen der Feldkardinalität
Nachrichtenfelder können einer der folgenden Typen sein:
Singular (einzeln):
In proto3 gibt es zwei Arten von singulären Feldern
optional: (empfohlen) Einoptional-Feld befindet sich in einem von zwei möglichen Zuständen- Das Feld ist gesetzt und enthält einen Wert, der explizit gesetzt oder aus dem Wire-Format gelesen wurde. Es wird an das Wire-Format serialisiert.
- Das Feld ist nicht gesetzt und gibt den Standardwert zurück. Es wird nicht an das Wire-Format serialisiert.
Sie können prüfen, ob der Wert explizit gesetzt wurde.
optionalwird gegenüber impliziten Feldern für maximale Kompatibilität mit Protobuf-Editionen und proto2 empfohlen.implizit: (nicht empfohlen) Ein implizites Feld hat keine explizite Kardinalitätsbezeichnung und verhält sich wie folgt
Wenn das Feld ein Nachrichtentyp ist, verhält es sich genauso wie ein
optional-Feld.Wenn das Feld keine Nachricht ist, hat es zwei Zustände
- Das Feld ist auf einen nicht-Standardwert (Nicht-Null) gesetzt, der explizit gesetzt oder von der Leitung geparst wurde. Es wird auf die Leitung serialisiert.
- Das Feld ist auf den Standardwert (Null) gesetzt. Es wird nicht auf die Leitung serialisiert. Tatsächlich können Sie nicht feststellen, ob der Standardwert (Null) gesetzt oder von der Leitung geparst wurde oder gar nicht vorhanden ist. Weitere Informationen zu diesem Thema finden Sie unter Feldpräsenz.
repeated(wiederholt): Dieser Feldtyp kann in einer wohlgeformten Nachricht null- oder mehrmals vorkommen. Die Reihenfolge der wiederholten Werte wird beibehalten.map(Zuordnung): Dies ist ein Feldtyp mit Schlüssel/Wert-Paaren. Weitere Informationen zu diesem Feldtyp finden Sie unter Maps.
Repeated-Felder sind standardmäßig gepackt
In proto3 verwenden repeated-Felder von skalaren numerischen Typen standardmäßig eine packed-Kodierung.
Weitere Informationen zur packed-Kodierung finden Sie unter Protocol Buffer-Kodierung.
Nachrichtentyp-Felder haben immer Feldpräsenz
In proto3 haben Nachrichtentyp-Felder bereits Feldpräsenz. Daher ändert die Hinzufügung des optional-Modifikators nichts an der Feldpräsenz für das Feld.
Die Definitionen für Message2 und Message3 im folgenden Codebeispiel generieren denselben Code für alle Sprachen, und es gibt keinen Unterschied in der Darstellung in Binär-, JSON- und TextFormat.
syntax="proto3";
package foo.bar;
message Message1 {}
message Message2 {
Message1 foo = 1;
}
message Message3 {
optional Message1 bar = 1;
}
Wohlgeformte Nachrichten
Der Begriff „wohlgeformt“, wenn er auf Protobuf-Nachrichten angewendet wird, bezieht sich auf die serialisierten/deserialisierten Bytes. Der protoc-Parser validiert, ob eine gegebene proto-Definitionsdatei lesbar ist.
Singuläre Felder können mehrfach in den Wire-Format-Bytes vorkommen. Der Parser akzeptiert die Eingabe, aber nur die letzte Instanz dieses Feldes ist über die generierten Bindings zugänglich. Weitere Informationen hierzu finden Sie unter Last One Wins.
Hinzufügen weiterer Nachrichtentypen
Mehrere Nachrichtentypen können in einer einzigen .proto-Datei definiert werden. Dies ist nützlich, wenn Sie mehrere zusammengehörige Nachrichten definieren – zum Beispiel, wenn Sie das Antwortnachrichtenformat definieren möchten, das Ihrer SearchResponse-Nachricht entspricht, können Sie es derselben .proto hinzufügen.
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
message SearchResponse {
...
}
Kombinieren von Nachrichten führt zu bloat Während mehrere Nachrichtentypen (wie Nachrichten, Enums und Dienste) in einer einzigen .proto-Datei definiert werden können, kann dies auch zu Abhängigkeits-Bloat führen, wenn eine große Anzahl von Nachrichten mit unterschiedlichen Abhängigkeiten in einer einzigen Datei definiert wird. Es wird empfohlen, so wenige Nachrichtentypen wie möglich pro .proto-Datei einzuschließen.
Hinzufügen von Kommentaren
So fügen Sie Ihren .proto-Dateien Kommentare hinzu:
Bevorzugen Sie Zeilenendkommentare im C/C++/Java-Stil „//“ auf der Zeile vor dem .proto-Codeelement
Inline-/Mehrzeilenkommentare im C-Stil
/* ... */werden ebenfalls akzeptiert.- Bei mehrzeiligen Kommentaren wird eine Randzeile mit „*“ bevorzugt.
/**
* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response.
*/
message SearchRequest {
string query = 1;
// Which page number do we want?
int32 page_number = 2;
// Number of results to return per page.
int32 results_per_page = 3;
}
Löschen von Feldern
Das Löschen von Feldern kann zu schwerwiegenden Problemen führen, wenn es nicht korrekt durchgeführt wird.
Wenn Sie ein Feld nicht mehr benötigen und alle Referenzen aus dem Client-Code gelöscht wurden, können Sie die Felddefinition aus der Nachricht entfernen. Sie müssen jedoch die Feldnummer des gelöschten Feldes reservieren. Wenn Sie die Feldnummer nicht reservieren, ist es möglich, dass ein Entwickler diese Nummer in Zukunft wiederverwendet.
Sie sollten auch den Feldnamen reservieren, um das Parsen von Nachrichten mit JSON- und TextFormat-Kodierungen weiterhin zu ermöglichen.
Reservierte Feldnummern
Wenn Sie einen Nachrichtentyp aktualisieren, indem Sie ein Feld vollständig löschen oder auskommentieren, können zukünftige Entwickler die Feldnummer wiederverwenden, wenn sie eigene Aktualisierungen an dem Typ vornehmen. Dies kann zu schwerwiegenden Problemen führen, wie in Folgen der Wiederverwendung von Feldnummern beschrieben. Um sicherzustellen, dass dies nicht geschieht, fügen Sie Ihre gelöschte Feldnummer zur reserved-Liste hinzu.
Der protoc-Compiler generiert Fehlermeldungen, wenn zukünftige Entwickler versuchen, diese reservierten Feldnummern zu verwenden.
message Foo {
reserved 2, 15, 9 to 11;
}
Reservierte Feldnummernbereiche sind inklusiv (9 bis 11 ist dasselbe wie 9, 10, 11).
Reservierte Feldnamen
Die spätere Wiederverwendung eines alten Feldnamens ist im Allgemeinen sicher, außer bei der Verwendung von TextProto- oder JSON-Kodierungen, bei denen der Feldname serialisiert wird. Um dieses Risiko zu vermeiden, können Sie den gelöschten Feldnamen zur reserved-Liste hinzufügen.
Reservierte Namen beeinflussen nur das Verhalten des protoc-Compilers und nicht das Laufzeitverhalten, mit einer Ausnahme: TextProto-Implementierungen können unbekannte Felder (ohne eine Fehlermeldung wie bei anderen unbekannten Feldern) mit reservierten Namen beim Parsen verwerfen (nur die C++- und Go-Implementierungen tun dies derzeit). Das Laufzeit-JSON-Parsing wird von reservierten Namen nicht beeinflusst.
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
Beachten Sie, dass Sie Feldnamen und Feldnummern nicht in derselben reserved-Anweisung mischen können.
Was wird aus Ihrer .proto-Datei generiert?
Wenn Sie den Protocol Buffer-Compiler für eine .proto-Datei ausführen, generiert der Compiler den Code in Ihrer gewählten Sprache, den Sie benötigen, um mit den in der Datei beschriebenen Nachrichtentypen zu arbeiten, einschließlich des Abrufens und Setzens von Feldwerten, des Serialisierens Ihrer Nachrichten in einen Ausgabestrom und des Parsens Ihrer Nachrichten aus einem Eingabestrom.
- Für C++ generiert der Compiler aus jeder
.proto-Datei eine.h- und eine.cc-Datei mit einer Klasse für jeden in Ihrer Datei beschriebenen Nachrichtentyp. - Für Java generiert der Compiler eine
.java-Datei mit einer Klasse für jeden Nachrichtentyp sowie eine spezielleBuilder-Klasse zum Erstellen von Nachrichtenklasseninstanzen. - Für Kotlin generiert der Compiler zusätzlich zum generierten Java-Code eine
.kt-Datei für jeden Nachrichtentyp mit einer verbesserten Kotlin-API. Dies beinhaltet eine DSL, die die Erstellung von Nachrichteninstanzen vereinfacht, einen nullable Feld-Accessor und eine Kopierfunktion. - Python ist etwas anders – der Python-Compiler generiert ein Modul mit einer statischen Beschreibung jedes Nachrichtentyps in Ihrer
.proto-Datei, die dann mit einer Metaklasse verwendet wird, um die notwendige Python-Datenzugriffsklasse zur Laufzeit zu erstellen. - Für Go generiert der Compiler eine
.pb.go-Datei mit einem Typ für jeden Nachrichtentyp in Ihrer Datei. - Für Ruby generiert der Compiler eine
.rb-Datei mit einem Ruby-Modul, das Ihre Nachrichtentypen enthält. - Für Objective-C generiert der Compiler aus jeder
.proto-Datei einepbobjc.h- und einepbobjc.m-Datei mit einer Klasse für jeden in Ihrer Datei beschriebenen Nachrichtentyp. - Für C# generiert der Compiler aus jeder
.proto-Datei eine.cs-Datei mit einer Klasse für jeden in Ihrer Datei beschriebenen Nachrichtentyp. - Für PHP generiert der Compiler eine
.php-Nachrichtendatei für jeden in Ihrer Datei beschriebenen Nachrichtentyp und eine.php-Metadatendatei für jede kompilierte.proto-Datei. Die Metadatendatei wird verwendet, um die gültigen Nachrichtentypen in den Deskriptorpool zu laden. - Für Dart generiert der Compiler eine
.pb.dart-Datei mit einer Klasse für jeden Nachrichtentyp in Ihrer Datei.
Weitere Informationen zur Verwendung der APIs für jede Sprache finden Sie im Tutorial für Ihre gewählte Sprache. Für noch detailliertere API-Informationen siehe die entsprechende API-Referenz.
Skalare Werttypen
Ein skalarer Nachrichtenfeld kann einen der folgenden Typen haben – die Tabelle zeigt den im .proto-Datei angegebenen Typ und den entsprechenden Typ in der automatisch generierten Klasse
| Proto-Typ | Hinweise |
|---|---|
| double | Verwendet das IEEE 754 Double-Precision-Format. |
| float | Verwendet das IEEE 754 Single-Precision-Format. |
| int32 | Verwendet variable Längen-Kodierung. Ineffizient für die Kodierung negativer Zahlen – wenn Ihr Feld wahrscheinlich negative Werte hat, verwenden Sie stattdessen sint32. |
| int64 | Verwendet variable Längen-Kodierung. Ineffizient für die Kodierung negativer Zahlen – wenn Ihr Feld wahrscheinlich negative Werte hat, verwenden Sie stattdessen sint64. |
| uint32 | Verwendet variable Längen-Kodierung. |
| uint64 | Verwendet variable Längen-Kodierung. |
| sint32 | Verwendet variable Längen-Kodierung. Signed-Int-Wert. Diese kodieren negative Zahlen effizienter als normale int32s. |
| sint64 | Verwendet variable Längen-Kodierung. Signed-Int-Wert. Diese kodieren negative Zahlen effizienter als normale int64s. |
| fixed32 | Immer vier Bytes. Effizienter als uint32, wenn Werte oft größer als 228 sind. |
| fixed64 | Immer acht Bytes. Effizienter als uint64, wenn Werte oft größer als 256 sind. |
| sfixed32 | Immer vier Bytes. |
| sfixed64 | Immer acht Bytes. |
| bool | |
| string | Eine Zeichenkette muss immer UTF-8-kodierten oder 7-Bit-ASCII-Text enthalten und darf nicht länger als 232 sein. |
| bytes | Kann jede beliebige Byte-Sequenz enthalten, die nicht länger als 232 ist. |
| Proto-Typ | C++-Typ | Java/Kotlin-Typ[1] | Python-Typ[3] | Go-Typ | Ruby-Typ | C#-Typ | PHP-Typ | Dart-Typ | Rust-Typ |
|---|---|---|---|---|---|---|---|---|---|
| double | double | double | float | float64 | Float | double | float | double | f64 |
| float | float | float | float | float32 | Float | float | float | double | f32 |
| int32 | int32_t | int | int | int32 | Fixnum oder Bignum (je nach Bedarf) | int | integer | int | i32 |
| int64 | int64_t | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 |
| uint32 | uint32_t | int[2] | int/long[4] | uint32 | Fixnum oder Bignum (je nach Bedarf) | uint | integer | int | u32 |
| uint64 | uint64_t | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 |
| sint32 | int32_t | int | int | int32 | Fixnum oder Bignum (je nach Bedarf) | int | integer | int | i32 |
| sint64 | int64_t | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 |
| fixed32 | uint32_t | int[2] | int/long[4] | uint32 | Fixnum oder Bignum (je nach Bedarf) | uint | integer | int | u32 |
| fixed64 | uint64_t | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 |
| sfixed32 | int32_t | int | int | int32 | Fixnum oder Bignum (je nach Bedarf) | int | integer | int | i32 |
| sfixed64 | int64_t | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 |
| bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | bool |
| string | std::string | String | str/unicode[5] | string | String (UTF-8) | string | string | String | ProtoString |
| bytes | std::string | ByteString | str (Python 2), bytes (Python 3) | []byte | String (ASCII-8BIT) | ByteString | string | List | ProtoBytes |
[1] Kotlin verwendet die entsprechenden Typen von Java, auch für vorzeichenlose Typen, um die Kompatibilität in gemischten Java/Kotlin-Codebasen zu gewährleisten.
[2] In Java werden vorzeichenlose 32-Bit- und 64-Bit-Integer mit ihren vorzeichenbehafteten Gegenstücken dargestellt, wobei das höchste Bit einfach im Vorzeichenbit gespeichert wird.
[3] In allen Fällen führt das Setzen von Werten auf ein Feld eine Typenprüfung durch, um sicherzustellen, dass es gültig ist.
[4] 64-Bit- oder vorzeichenlose 32-Bit-Integer werden beim Dekodieren immer als long dargestellt, können aber int sein, wenn beim Setzen des Feldes ein int angegeben wird. In allen Fällen muss der Wert beim Setzen in den dargestellten Typ passen. Siehe [2].
[5] Python-Strings werden beim Dekodieren als Unicode dargestellt, können aber str sein, wenn ein ASCII-String angegeben wird (dies kann sich ändern).
[6] Integer wird auf 64-Bit-Maschinen verwendet und String auf 32-Bit-Maschinen.
Wie diese Typen beim Serialisieren Ihrer Nachricht kodiert werden, erfahren Sie unter Protocol Buffer-Kodierung.
Standard-Feldwerte
Wenn eine Nachricht geparst wird und die kodierten Nachrichtendaten kein bestimmtes Feld enthalten, gibt der Zugriff auf dieses Feld im geparsten Objekt den Standardwert für dieses Feld zurück. Die Standardwerte sind typspezifisch:
- Für Zeichenketten ist der Standardwert die leere Zeichenkette.
- Für Bytes ist der Standardwert ein leeres Byte-Array.
- Für Boolesche Werte ist der Standardwert false.
- Für numerische Typen ist der Standardwert Null.
- Bei Nachrichtenfeldern ist das Feld nicht gesetzt. Sein genauer Wert ist sprachabhängig. Details finden Sie im Leitfaden für generierten Code.
- Bei Enums ist der Standardwert der erste definierte Enum-Wert, der 0 sein muss. Siehe Enum-Standardwert.
Der Standardwert für wiederholte Felder ist leer (im Allgemeinen eine leere Liste in der entsprechenden Sprache).
Der Standardwert für Map-Felder ist leer (im Allgemeinen eine leere Map in der entsprechenden Sprache).
Beachten Sie, dass bei skalaren Feldern mit impliziter Präsenz, sobald eine Nachricht geparst ist, nicht mehr feststellbar ist, ob das Feld explizit auf den Standardwert gesetzt wurde (z. B. ob ein Boolescher Wert auf false gesetzt wurde) oder gar nicht gesetzt wurde: Dies sollten Sie bei der Definition Ihrer Nachrichtentypen berücksichtigen. Haben Sie beispielsweise kein Boolean, das ein bestimmtes Verhalten beim Setzen auf false auslöst, wenn Sie nicht möchten, dass dieses Verhalten auch standardmäßig auftritt. Beachten Sie auch, dass wenn ein skalarer Nachrichtenfeld auf seinen Standardwert gesetzt ist, der Wert nicht auf der Leitung serialisiert wird. Wenn ein Float- oder Double-Wert auf +0 gesetzt ist, wird er nicht serialisiert, aber -0 wird als unterschiedlich betrachtet und serialisiert.
Weitere Details zur Funktionsweise von Standardwerten im generierten Code finden Sie im generierten Code-Leitfaden für Ihre gewählte Sprache.
Aufzählungen (Enums)
Wenn Sie einen Nachrichtentyp definieren, möchten Sie vielleicht, dass eines seiner Felder nur einen von einer vordefinierten Liste von Werten haben kann. Angenommen, Sie möchten ein Feld corpus für jede SearchRequest hinzufügen, wobei der Korpus UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS oder VIDEO sein kann. Dies können Sie sehr einfach tun, indem Sie Ihrer Nachrichtendefinition ein enum mit einer Konstanten für jeden möglichen Wert hinzufügen.
Im folgenden Beispiel haben wir ein enum namens Corpus mit allen möglichen Werten und ein Feld vom Typ Corpus hinzugefügt.
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
Corpus corpus = 4;
}
Standardwert für Enum
Der Standardwert für das Feld SearchRequest.corpus ist CORPUS_UNSPECIFIED, da dies der erste in der Enum definierte Wert ist.
In proto3 muss der erste in einer Enum-Definition definierte Wert den Wert Null haben und sollte den Namen ENUM_TYPE_NAME_UNSPECIFIED oder ENUM_TYPE_NAME_UNKNOWN haben. Dies liegt daran, dass
- Es muss einen Null-Wert geben, damit wir 0 als numerischen Standardwert verwenden können.
- Der Null-Wert muss das erste Element sein, um mit den proto2-Semantiken kompatibel zu sein, bei denen der erste Enum-Wert der Standard ist, es sei denn, ein anderer Wert wurde explizit angegeben.
Es wird außerdem empfohlen, dass dieser erste Standardwert keine semantische Bedeutung hat, außer „dieser Wert wurde nicht angegeben“.
Enum-Wert-Aliase
Sie können Aliase definieren, indem Sie denselben Wert verschiedenen Enum-Konstanten zuweisen. Dazu müssen Sie die Option allow_alias auf true setzen. Andernfalls generiert der Protocol Buffer-Compiler eine Warnmeldung, wenn Aliase gefunden werden. Obwohl alle Alias-Werte für die Serialisierung gültig sind, wird nur der erste Wert beim Deserialisieren verwendet.
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
// ENAA_RUNNING = 1; // Uncommenting this line will cause a warning message.
ENAA_FINISHED = 2;
}
Enumeratoren-Konstanten müssen im Bereich einer 32-Bit-Ganzzahl liegen. Da enum-Werte Varint-Kodierung im Wire-Format verwenden, sind negative Werte ineffizient und daher nicht empfohlen. Sie können enums innerhalb einer Nachrichtendefinition definieren, wie im vorherigen Beispiel, oder außerhalb – diese enums können in jeder Nachrichtendefinition in Ihrer .proto-Datei wiederverwendet werden. Sie können auch einen in einer Nachricht deklarierten enum-Typ als Typ eines Feldes in einer anderen Nachricht verwenden, indem Sie die Syntax _MessageType_._EnumType_ verwenden.
Wenn Sie den Protocol Buffer-Compiler für eine .proto-Datei ausführen, die ein enum verwendet, generiert der Code für Java, Kotlin oder C++ ein entsprechendes enum oder eine spezielle EnumDescriptor-Klasse für Python, die zur Erstellung einer Reihe von symbolischen Konstanten mit ganzzahligen Werten in der zur Laufzeit generierten Klasse verwendet wird.
Wichtig
Der generierte Code kann sprachspezifischen Einschränkungen hinsichtlich der Anzahl der Enumeratoren unterliegen (einige Tausend für eine Sprache). Überprüfen Sie die Einschränkungen für die von Ihnen zu verwendenden Sprachen.Während der Deserialisierung werden nicht erkannte Enum-Werte in der Nachricht beibehalten, obwohl die Darstellung, wie dies geschieht, wenn die Nachricht deserialisiert wird, sprachabhängig ist. In Sprachen, die offene Enum-Typen mit Werten außerhalb des Bereichs der angegebenen Symbole unterstützen, wie z. B. C++ und Go, wird der unbekannte Enum-Wert einfach als seine zugrunde liegende ganzzahlige Darstellung gespeichert. In Sprachen mit geschlossenen Enum-Typen wie Java wird ein Fall in der Enum verwendet, um einen nicht erkannten Wert darzustellen, und die zugrunde liegende Ganzzahl kann über spezielle Accessoren abgerufen werden. In beiden Fällen wird, wenn die Nachricht serialisiert wird, der nicht erkannte Wert weiterhin mit der Nachricht serialisiert.
Wichtig
Informationen darüber, wie Enums funktionieren sollten, im Gegensatz dazu, wie sie derzeit in verschiedenen Sprachen funktionieren, finden Sie unter Enum-Verhalten.Weitere Informationen zur Arbeit mit Nachrichten-enums in Ihren Anwendungen finden Sie im generierten Code-Leitfaden für Ihre gewählte Sprache.
Reservierte Werte
Wenn Sie einen Enum-Typ aktualisieren, indem Sie einen Enum-Eintrag vollständig entfernen oder auskommentieren, können zukünftige Benutzer den numerischen Wert bei eigenen Aktualisierungen des Typs wiederverwenden. Dies kann zu schwerwiegenden Problemen führen, wenn sie später alte Instanzen derselben .proto laden, einschließlich Datenbeschädigung, Datenschutzfehlern usw. Eine Möglichkeit, dies zu verhindern, besteht darin, anzugeben, dass die numerischen Werte (und/oder Namen, die ebenfalls Probleme bei der JSON-Serialisierung verursachen können) Ihrer gelöschten Einträge reserved sind. Der Protocol Buffer-Compiler wird sich beschweren, wenn zukünftige Benutzer diese Bezeichner verwenden. Sie können angeben, dass Ihr reservierter numerischer Wertebereich mit dem Schlüsselwort max bis zum maximal möglichen Wert reicht.
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
Beachten Sie, dass Sie Feldnamen und numerische Werte nicht in derselben reserved-Anweisung mischen können.
Verwenden anderer Nachrichtentypen
Sie können andere Nachrichtentypen als Feldtypen verwenden. Angenommen, Sie möchten beispielsweise Result-Nachrichten in jeder SearchResponse-Nachricht enthalten – dazu können Sie einen Result-Nachrichtentyp in derselben .proto-Datei definieren und dann ein Feld vom Typ Result in SearchResponse angeben.
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
Importieren von Definitionen
Im vorherigen Beispiel ist der Result-Nachrichtentyp in derselben Datei wie SearchResponse definiert – was ist, wenn der Nachrichtentyp, den Sie als Feldtyp verwenden möchten, bereits in einer anderen .proto-Datei definiert ist?
Sie können Definitionen aus anderen .proto-Dateien verwenden, indem Sie sie importieren. Um Definitionen aus anderen .proto-Dateien zu importieren, fügen Sie eine Importanweisung am Anfang Ihrer Datei hinzu.
import "myproject/other_protos.proto";
Der Protobuf-Compiler sucht nach importierten Dateien in einer Reihe von Verzeichnissen, die über das Flag -I/--proto_path angegeben werden. Der Pfad in einer import-Anweisung wird relativ zu diesen Verzeichnissen aufgelöst. Weitere Informationen zur Verwendung des Compilers finden Sie unter Generieren Ihrer Klassen.
Betrachten Sie beispielsweise die folgende Verzeichnisstruktur
my_project/
├── protos/
│ ├── main.proto
│ └── common/
│ └── timestamp.proto
Um Definitionen aus timestamp.proto innerhalb von main.proto zu verwenden, würden Sie den Compiler vom Verzeichnis my_project aus ausführen und --proto_path=protos setzen. Die import-Anweisung in main.proto wäre dann
// Located in my_project/protos/main.proto
import "common/timestamp.proto";
Im Allgemeinen sollten Sie das Flag --proto_path auf das Verzeichnis auf der höchsten Ebene setzen, das Protobufs enthält. Dies ist oft die Wurzel des Projekts, aber in diesem Beispiel befindet es sich in einem separaten Verzeichnis /protos.
Standardmäßig können Sie nur Definitionen aus direkt importierten .proto-Dateien verwenden. Manchmal müssen Sie jedoch eine .proto-Datei an einen neuen Ort verschieben. Anstatt die .proto-Datei direkt zu verschieben und alle Aufrufe in einer einzigen Änderung zu aktualisieren, können Sie eine Platzhalter-.proto-Datei am alten Speicherort platzieren, um alle Importe mithilfe des Konzepts import public an den neuen Speicherort weiterzuleiten.
Hinweis: Die in Java verfügbare Funktionalität für öffentliche Imports ist am effektivsten, wenn eine gesamte .proto-Datei verschoben wird oder wenn java_multiple_files = true verwendet wird. In diesen Fällen bleiben die generierten Namen stabil, sodass Referenzen in Ihrem Code nicht aktualisiert werden müssen. Obwohl technisch funktional, wenn ein Teil einer .proto-Datei ohne java_multiple_files = true verschoben wird, erfordert dies gleichzeitige Aktualisierungen vieler Referenzen und erleichtert daher möglicherweise nicht wesentlich die Migration. Die Funktionalität ist in Kotlin, TypeScript, JavaScript, GCL oder mit C++-Zielen, die die statische Reflexion von Protobuf verwenden, nicht verfügbar.
import public-Abhängigkeiten können von jedem Code, der das Proto mit der import public-Anweisung importiert, transitiv genutzt werden. Zum Beispiel:
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
Proto2-Nachrichtentypen verwenden
Es ist möglich, proto2-Nachrichtentypen zu importieren und sie in Ihren proto3-Nachrichten zu verwenden und umgekehrt. Proto2-Enums können jedoch nicht direkt in der proto3-Syntax verwendet werden (es ist in Ordnung, wenn eine importierte proto2-Nachricht sie verwendet).
Verschachtelte Typen
Sie können Nachrichtentypen innerhalb anderer Nachrichtentypen definieren und verwenden, wie im folgenden Beispiel – hier wird die Nachricht Result innerhalb der Nachricht SearchResponse definiert
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
Wenn Sie diesen Nachrichtentyp außerhalb seines übergeordneten Nachrichtentyps wiederverwenden möchten, verweisen Sie darauf als _Elternteil_._Typ_
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
Sie können Nachrichten beliebig tief verschachteln. Beachten Sie im folgenden Beispiel, dass die beiden verschachtelten Typen namens Inner völlig unabhängig voneinander sind, da sie innerhalb verschiedener Nachrichten definiert sind
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
int32 ival = 1;
bool booly = 2;
}
}
}
Nachrichtentyp aktualisieren
Wenn ein bestehender Nachrichtentyp nicht mehr all Ihren Anforderungen entspricht – Sie möchten beispielsweise, dass das Nachrichtenformat ein zusätzliches Feld hat –, aber Sie möchten immer noch Code verwenden, der mit dem alten Format erstellt wurde, machen Sie sich keine Sorgen! Es ist sehr einfach, Nachrichtentypen zu aktualisieren, ohne Ihren bestehenden Code zu beeinträchtigen, wenn Sie das binäre Wire-Format verwenden.
Hinweis
Wenn Sie ProtoJSON oder Proto-Textformat zum Speichern Ihrer Protokollpuffer-Nachrichten verwenden, sind die Änderungen, die Sie in Ihrer Proto-Definition vornehmen können, unterschiedlich. Die sicheren Änderungen des ProtoJSON-Wire-Formats sind hier beschrieben.Prüfen Sie Proto-Best Practices und die folgenden Regeln
Binärdraht-unsichere Änderungen
Wire-unsichere Änderungen sind Schemaänderungen, die fehlschlagen, wenn Sie Daten parsen, die mit dem alten Schema serialisiert wurden, mit einem Parser, der das neue Schema verwendet (oder umgekehrt). Nehmen Sie nur Wire-unsichere Änderungen vor, wenn Sie wissen, dass alle Serialisierer und Deserialisierer der Daten das neue Schema verwenden.
- Das Ändern von Feldnummern für vorhandene Felder ist nicht sicher.
- Das Ändern der Feldnummer entspricht dem Löschen des Feldes und dem Hinzufügen eines neuen Feldes mit demselben Typ. Wenn Sie ein Feld neu nummerieren möchten, lesen Sie die Anweisungen zum Löschen eines Feldes.
- Das Verschieben von Feldern in eine vorhandene
oneofist nicht sicher.
Binärdraht-sichere Änderungen
Wire-sichere Änderungen sind solche, bei denen es völlig sicher ist, das Schema auf diese Weise zu entwickeln, ohne Risiko von Datenverlust oder neuen Parsing-Fehlern.
Beachten Sie, dass alle Wire-sicheren Änderungen eine breaking change für den Anwendungscode in einer bestimmten Sprache darstellen können. Beispielsweise wäre das Hinzufügen eines Wertes zu einem bereits vorhandenen Enum ein Kompilierungsfehler für jeden Code mit einem erschöpfenden Switch auf diesem Enum. Aus diesem Grund vermeidet Google möglicherweise einige dieser Änderungen an öffentlichen Nachrichten: Die AIPs enthalten Anleitungen, welche dieser Änderungen dort sicher vorgenommen werden können.
- Das Hinzufügen neuer Felder ist sicher.
- Wenn Sie neue Felder hinzufügen, können alle von Code serialisierten Nachrichten, die Ihr „altes“ Nachrichtenformat verwenden, immer noch von Ihrem neuen generierten Code geparst werden. Sie sollten die Standardwerte für diese Elemente im Auge behalten, damit neuer Code ordnungsgemäß mit von altem Code generierten Nachrichten interagieren kann. Ebenso können von Ihrem neuen Code erstellte Nachrichten von Ihrem alten Code geparst werden: Alte Binärdateien ignorieren neue Felder beim Parsen einfach. Weitere Details finden Sie im Abschnitt Unbekannte Felder.
- Das Entfernen von Feldern ist sicher.
- Derselbe Feldnummer darf in Ihrem aktualisierten Nachrichtentyp nicht wiederverwendet werden. Möglicherweise möchten Sie das Feld stattdessen umbenennen, vielleicht mit dem Präfix „OBSOLETE_“, oder die Feldnummer reservieren, damit zukünftige Benutzer Ihres
.protodie Nummer nicht versehentlich wiederverwenden können.
- Derselbe Feldnummer darf in Ihrem aktualisierten Nachrichtentyp nicht wiederverwendet werden. Möglicherweise möchten Sie das Feld stattdessen umbenennen, vielleicht mit dem Präfix „OBSOLETE_“, oder die Feldnummer reservieren, damit zukünftige Benutzer Ihres
- Das Hinzufügen zusätzlicher Werte zu einem Enum ist sicher.
- Das Ändern eines einzelnen expliziten Präsenzfeldes oder einer Erweiterung in ein Mitglied einer **neuen**
oneofist sicher. - Das Ändern einer
oneof, die nur ein Feld enthält, in ein explizites Präsenzfeld ist sicher. - Das Ändern eines Feldes in eine Erweiterung mit derselben Nummer und demselben Typ ist sicher.
Binärdraht-kompatible Änderungen (bedingt sicher)
Im Gegensatz zu Wire-sicheren Änderungen bedeutet Wire-kompatibel, dass dieselben Daten sowohl vor als auch nach einer gegebenen Änderung geparst werden können. Das Parsen der Daten kann jedoch bei dieser Art von Änderung verlustbehaftet sein. Beispielsweise ist die Änderung eines int32 in ein int64 eine kompatible Änderung, aber wenn ein Wert größer als INT32_MAX geschrieben wird, verwirft ein Client, der ihn als int32 liest, die höherwertigen Bits der Zahl.
Sie können kompatible Änderungen an Ihrem Schema nur vornehmen, wenn Sie die Einführung in Ihrem System sorgfältig verwalten. Sie können beispielsweise ein int32 in ein int64 ändern, aber sicherstellen, dass Sie weiterhin nur gültige int32-Werte schreiben, bis das neue Schema auf alle Endpunkte angewendet wurde, und dann anschließend größere Werte schreiben.
Wenn Ihr Schema außerhalb Ihrer Organisation veröffentlicht wird, sollten Sie im Allgemeinen keine Wire-kompatiblen Änderungen vornehmen, da Sie die Einführung des neuen Schemas nicht verwalten können, um zu wissen, wann der unterschiedliche Wertebereich sicher verwendet werden kann.
int32,uint32,int64,uint64undboolsind alle kompatibel.- Wenn eine Zahl von der Wire gelesen wird, die nicht in den entsprechenden Typ passt, erhalten Sie denselben Effekt, als ob Sie die Zahl in C++ in diesen Typ umgewandelt hätten (z. B. wenn eine 64-Bit-Zahl als int32 gelesen wird, wird sie auf 32 Bit abgeschnitten).
sint32undsint64sind miteinander kompatibel, aber **nicht** mit den anderen Ganzzahltypen.- Wenn der geschriebene Wert zwischen INT_MIN und INT_MAX (einschließlich) lag, wird er mit beiden Typen als derselbe Wert geparst. Wenn ein sint64-Wert außerhalb dieses Bereichs geschrieben und als sint32 geparst wurde, wird der Varint auf 32 Bit abgeschnitten und dann die Zickzack-Dekodierung durchgeführt (was zu einem anderen beobachteten Wert führt).
stringundbytessind kompatibel, solange die Bytes gültiges UTF-8 sind.- Eingebettete Nachrichten sind mit
byteskompatibel, wenn die Bytes eine kodierte Instanz der Nachricht enthalten. fixed32ist mitsfixed32undfixed64mitsfixed64kompatibel.- Für
string,bytesund Nachrichtenfelder ist singulär mitrepeatedkompatibel.- Bei serialisierten Daten eines wiederholten Feldes als Eingabe nehmen Clients, die dieses Feld als singulär erwarten, den letzten Eingabewert, wenn es sich um ein primitives Feld handelt, oder zusammenführen alle Eingabeelemente, wenn es sich um ein Nachrichtenfeld handelt. Beachten Sie, dass dies für numerische Typen, einschließlich Boolescher Werte und Enums, **nicht** generell sicher ist. Wiederholte Felder von numerischen Typen werden standardmäßig im packed-Format serialisiert, das nicht korrekt geparst wird, wenn ein singuläres Feld erwartet wird.
enumist kompatibel mitint32,uint32,int64unduint64- Beachten Sie, dass Client-Code sie möglicherweise unterschiedlich behandelt, wenn die Nachricht deserialisiert wird: Beispielsweise werden nicht erkannte Proto3-
enum-Werte in der Nachricht beibehalten, aber wie dies bei der Deserialisierung der Nachricht dargestellt wird, ist sprachabhängig.
- Beachten Sie, dass Client-Code sie möglicherweise unterschiedlich behandelt, wenn die Nachricht deserialisiert wird: Beispielsweise werden nicht erkannte Proto3-
- Das Ändern eines Feldes von einer
map<K, V>zu dem entsprechendenrepeated-Nachrichtenfeld ist binär kompatibel (siehe Maps, unten, für das Nachrichtenlayout und andere Einschränkungen).- Die Sicherheit der Änderung hängt jedoch von der Anwendung ab: Beim Deserialisieren und erneuten Serialisieren einer Nachricht erzeugen Clients, die die
repeated-Felddefinition verwenden, ein semantisch identisches Ergebnis; Clients, die diemap-Felddefinition verwenden, können jedoch Einträge neu anordnen und Einträge mit doppelten Schlüsseln verwerfen.
- Die Sicherheit der Änderung hängt jedoch von der Anwendung ab: Beim Deserialisieren und erneuten Serialisieren einer Nachricht erzeugen Clients, die die
Unbekannte Felder
Unbekannte Felder sind wohlgeformte Protokollpuffer-serialisierte Daten, die Felder darstellen, die der Parser nicht erkennt. Wenn beispielsweise ein altes Binärformat Daten parst, die von einem neuen Binärformat mit neuen Feldern gesendet wurden, werden diese neuen Felder im alten Binärformat zu unbekannten Feldern.
Proto3-Nachrichten behalten unbekannte Felder bei und schließen sie beim Parsen und in der serialisierten Ausgabe ein, was dem Verhalten von proto2 entspricht.
Beibehalten unbekannter Felder
Einige Aktionen können dazu führen, dass unbekannte Felder verloren gehen. Wenn Sie beispielsweise eine der folgenden Aktionen ausführen, gehen unbekannte Felder verloren
- Serialisieren eines Proto in JSON.
- Iterieren über alle Felder in einer Nachricht, um eine neue Nachricht zu füllen.
Um den Verlust unbekannter Felder zu vermeiden, gehen Sie wie folgt vor
- Verwenden Sie binär; vermeiden Sie die Verwendung von Textformaten für den Datenaustausch.
- Verwenden Sie nachrichtenorientierte APIs wie
CopyFrom()undMergeFrom(), um Daten zu kopieren, anstatt Feld für Feld zu kopieren
TextFormat ist ein Sonderfall. Das Serialisieren in TextFormat gibt unbekannte Felder mit ihren Feldnummern aus. Das Parsen von TextFormat-Daten zurück in ein Binär-Proto schlägt jedoch fehl, wenn Einträge mit Feldnummern vorhanden sind.
Any
Der Any-Nachrichtentyp ermöglicht es Ihnen, Nachrichten als eingebettete Typen zu verwenden, ohne deren .proto-Definition zu haben. Eine Any enthält eine beliebige serialisierte Nachricht als bytes, zusammen mit einer URL, die als global eindeutiger Identifikator für die Nachricht dient und auf deren Typ verweist. Um den Typ Any zu verwenden, müssen Sie google/protobuf/any.proto importieren.
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
Die Standard-Typ-URL für einen gegebenen Nachrichtentyp ist type.googleapis.com/_packagename_._messagename_.
Verschiedene Sprachimplementierungen unterstützen Laufzeitbibliothekshelfer zum packen und entpacken von Any-Werten auf typsichere Weise – z. B. hat der Any-Typ in Java spezielle pack()- und unpack()-Accessoren, während es in C++ PackFrom()- und UnpackTo()-Methoden gibt
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const google::protobuf::Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
Oneof
Wenn Sie eine Nachricht mit vielen singulären Feldern haben und zu jedem Zeitpunkt höchstens ein Feld gesetzt sein wird, können Sie dieses Verhalten erzwingen und Speicher sparen, indem Sie die Oneof-Funktion verwenden.
Oneof-Felder sind wie optionale Felder, mit dem Unterschied, dass sich alle Felder in einem Oneof denselben Speicherplatz teilen und höchstens ein Feld gleichzeitig gesetzt sein kann. Das Setzen eines beliebigen Mitglieds eines Oneof löscht automatisch alle anderen Mitglieder. Sie können überprüfen, welcher Wert in einem Oneof gesetzt ist (falls vorhanden) mithilfe einer speziellen case()- oder WhichOneof()-Methode, abhängig von der gewählten Sprache.
Beachten Sie, dass **wenn mehrere Werte gesetzt sind, der zuletzt gesetzte Wert, wie durch die Reihenfolge im Proto bestimmt, alle vorherigen überschreibt**.
Feldnummern für Oneof-Felder müssen innerhalb der umschließenden Nachricht eindeutig sein.
Verwenden von Oneof
Um ein Oneof in Ihrer .proto-Datei zu definieren, verwenden Sie das Schlüsselwort oneof gefolgt von Ihrem Oneof-Namen, in diesem Fall test_oneof
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
Anschließend fügen Sie Ihre Oneof-Felder zur Oneof-Definition hinzu. Sie können Felder jeden Typs hinzufügen, außer map-Feldern und repeated-Feldern. Wenn Sie ein wiederholtes Feld zu einem Oneof hinzufügen müssen, können Sie eine Nachricht verwenden, die das wiederholte Feld enthält.
In Ihrem generierten Code haben Oneof-Felder dieselben Getter und Setter wie normale Felder. Sie erhalten auch eine spezielle Methode, um zu überprüfen, welcher Wert (falls vorhanden) im Oneof gesetzt ist. Weitere Informationen zur Oneof-API für Ihre gewählte Sprache finden Sie in der entsprechenden API-Referenz.
Oneof-Funktionen
Das Setzen eines Oneof-Feldes löscht automatisch alle anderen Mitglieder des Oneof. Wenn Sie also mehrere Oneof-Felder setzen, behält nur das **letzte** gesetzte Feld seinen Wert.
SampleMessage message; message.set_name("name"); CHECK_EQ(message.name(), "name"); // Calling mutable_sub_message() will clear the name field and will set // sub_message to a new instance of SubMessage with none of its fields set. message.mutable_sub_message(); CHECK(message.name().empty());Wenn der Parser mehrere Mitglieder desselben Oneof auf der Wire findet, wird nur das zuletzt gefundene Mitglied in der geparsten Nachricht verwendet. Beim Parsen von Daten auf der Wire, beginnend am Anfang der Bytes, werten Sie den nächsten Wert aus und wenden die folgenden Parsing-Regeln an
Prüfen Sie zuerst, ob ein **anderes** Feld im selben Oneof derzeit gesetzt ist, und löschen Sie es, falls ja.
Wenden Sie dann die Inhalte so an, als ob das Feld nicht in einem Oneof wäre
- Ein Primitiv überschreibt jeden bereits gesetzten Wert
- Eine Nachricht wird in jeden bereits gesetzten Wert gemergt
Ein Oneof kann nicht
repeatedsein.Reflection-APIs funktionieren für Oneof-Felder.
Wenn Sie ein Oneof-Feld auf seinen Standardwert setzen (z. B. ein int32-Oneof-Feld auf 0 setzen), wird der „Fall“ dieses Oneof-Feldes gesetzt und der Wert auf der Wire serialisiert.
Wenn Sie C++ verwenden, stellen Sie sicher, dass Ihr Code keine Speicherabstürze verursacht. Der folgende Beispielcode wird abstürzen, da
sub_messagedurch den Aufruf der Methodeset_name()bereits gelöscht wurde.SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes hereAuch in C++ gilt: Wenn Sie zwei Nachrichten mit Oneofs
Swap(), dann hat jede Nachricht am Ende den Oneof-Fall der anderen: Im folgenden Beispiel hatmsg1einesub_messageundmsg2hat einenname.SampleMessage msg1; msg1.set_name("name"); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK_EQ(msg2.name(), "name");
Abwärtskompatibilitätsprobleme
Seien Sie vorsichtig beim Hinzufügen oder Entfernen von Oneof-Feldern. Wenn die Überprüfung des Wertes eines Oneof None/NOT_SET zurückgibt, könnte dies bedeuten, dass das Oneof nicht gesetzt wurde oder auf ein Feld in einer anderen Version des Oneof gesetzt wurde. Es gibt keinen Unterschied, da es keine Möglichkeit gibt zu wissen, ob ein unbekanntes Feld auf der Wire ein Mitglied des Oneof ist.
Probleme mit der Wiederverwendung von Tags
- Singuläre Felder in oder aus einem Oneof verschieben: Sie können einige Ihrer Informationen verlieren (einige Felder werden gelöscht), nachdem die Nachricht serialisiert und geparst wurde. Sie können jedoch ein einzelnes Feld sicher in ein neues Oneof verschieben und möglicherweise mehrere Felder verschieben, wenn bekannt ist, dass nur eines davon jemals gesetzt ist. Siehe Nachrichtentyp aktualisieren für weitere Details.
- Löschen eines Oneof-Feldes und Wiedereinfügen: Dies kann dazu führen, dass Ihr aktuell gesetztes Oneof-Feld nach dem Serialisieren und Parsen der Nachricht gelöscht wird.
- Oneof aufteilen oder zusammenführen: Dies birgt ähnliche Probleme wie das Verschieben singulärer Felder.
Maps (Zuordnungen)
Wenn Sie eine assoziative Zuordnung als Teil Ihrer Datendefinition erstellen möchten, bietet Protobuf eine praktische Kurzschreibweise
map<key_type, value_type> map_field = N;
…wobei der key_type jeder ganzzahlige oder Zeichenfolgentyp sein kann (also jeder skalare Typ außer Gleitkommatypen und bytes). Beachten Sie, dass weder Enum noch Proto-Nachrichten für key_type gültig sind. Der value_type kann jeder Typ sein, außer einer anderen Map.
Wenn Sie beispielsweise eine Map von Projekten erstellen möchten, bei denen jede Project-Nachricht mit einem Zeichenfolgen-Schlüssel verbunden ist, könnten Sie sie wie folgt definieren
map<string, Project> projects = 3;
Map-Funktionen
- Map-Felder dürfen nicht
repeatedsein. - Die Reihenfolge des Wire-Formats und die Iterationsreihenfolge der Map-Werte sind undefiniert, sodass Sie sich nicht auf eine bestimmte Reihenfolge Ihrer Map-Elemente verlassen können.
- Beim Generieren von Textformat für eine
.proto-Datei werden Maps nach Schlüssel sortiert. Numerische Schlüssel werden numerisch sortiert. - Beim Parsen von der Wire oder beim Zusammenführen wird bei doppelten Map-Schlüsseln der zuletzt gesehene Schlüssel verwendet. Beim Parsen einer Map aus dem Textformat kann das Parsen fehlschlagen, wenn doppelte Schlüssel vorhanden sind.
- Wenn Sie einen Schlüssel, aber keinen Wert für ein Map-Feld angeben, ist das Verhalten beim Serialisieren des Feldes sprachabhängig. In C++, Java, Kotlin und Python wird der Standardwert des Typs serialisiert, während in anderen Sprachen nichts serialisiert wird.
- Kein Symbol
FooEntrykann im selben Geltungsbereich wie eine Mapfooexistieren, daFooEntrybereits von der Implementierung der Map verwendet wird.
Die generierte Map-API ist derzeit für alle unterstützten Sprachen verfügbar. Weitere Informationen zur Map-API für Ihre gewählte Sprache finden Sie in der entsprechenden API-Referenz.
Abwärtskompatibilität
Die Map-Syntax ist auf der Wire äquivalent zu Folgendem, sodass Protobuf-Implementierungen, die Maps nicht unterstützen, Ihre Daten immer noch verarbeiten können
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
Jede Protobuf-Implementierung, die Maps unterstützt, muss sowohl Daten produzieren als auch akzeptieren, die von der früheren Definition akzeptiert werden können.
Pakete (Packages)
Sie können eine optionale package-Spezifikation zu einer .proto-Datei hinzufügen, um Namenskonflikte zwischen Protokollnachrichtentypen zu vermeiden.
package foo.bar;
message Open { ... }
Sie können dann die Paket-Spezifikation beim Definieren von Feldern Ihres Nachrichtentyps verwenden
message Foo {
...
foo.bar.Open open = 1;
...
}
Die Art und Weise, wie eine Paket-Spezifikation den generierten Code beeinflusst, hängt von Ihrer gewählten Sprache ab
- In **C++** werden die generierten Klassen in einen C++-Namespace eingeschlossen. Beispielsweise wäre
Openim Namespacefoo::bar. - In **Java** und **Kotlin** wird das Paket als Java-Paket verwendet, es sei denn, Sie geben explizit eine
option java_packagein Ihrer.proto-Datei an. - In **Python** wird die Direktive
packageignoriert, da Python-Module nach ihrem Speicherort im Dateisystem organisiert sind. - In **Go** wird die Direktive
packageignoriert, und die generierte Datei.pb.gobefindet sich im Paket, das nach der entsprechendengo_proto_libraryBazel-Regel benannt ist. Für Open-Source-Projekte **müssen** Sie entweder einego_package-Option angeben oder das Bazel-Flag-Msetzen. - In **Ruby** werden die generierten Klassen in verschachtelte Ruby-Namespaces eingeschlossen, die in den erforderlichen Ruby-Namenskonventionstil (erstes Zeichen groß; wenn das erste Zeichen kein Buchstabe ist, wird
PB_vorangestellt) konvertiert werden. Zum Beispiel wäreOpenim NamespaceFoo::Bar. - In **PHP** wird das Paket als Namespace nach der Konvertierung in PascalCase verwendet, es sei denn, Sie geben explizit eine
option php_namespacein Ihrer.proto-Datei an. Zum Beispiel wäreOpenim NamespaceFoo\Bar. - In **C#** wird das Paket als Namespace nach der Konvertierung in PascalCase verwendet, es sei denn, Sie geben explizit eine
option csharp_namespacein Ihrer.proto-Datei an. Zum Beispiel wäreOpenim NamespaceFoo.Bar.
Beachten Sie, dass, auch wenn die Direktive package den generierten Code nicht direkt beeinflusst (z. B. in Python), es dennoch dringend empfohlen wird, das Paket für die .proto-Datei anzugeben, da dies andernfalls zu Namenskonflikten in Deskriptoren führen und das Proto für andere Sprachen nicht portierbar machen kann.
Pakete und Namensauflösung
Die Typnamensauflösung in der Protokollpuffer-Sprache funktioniert wie in C++: Zuerst wird der innerste Geltungsbereich durchsucht, dann der nächstinnere usw., wobei jedes Paket als „innerhalb“ seines übergeordneten Pakets betrachtet wird. Ein führender Punkt (z. B. .foo.bar.Baz) bedeutet, stattdessen vom äußersten Geltungsbereich aus zu starten.
Der Protokollpuffer-Compiler löst alle Typnamen auf, indem er die importierten .proto-Dateien parst. Der Code-Generator für jede Sprache weiß, wie auf jeden Typ in dieser Sprache verwiesen werden kann, auch wenn er unterschiedliche Geltungsbereichsregeln hat.
Definieren von Diensten
Wenn Sie Ihre Nachrichtentypen mit einem RPC-System (Remote Procedure Call) verwenden möchten, können Sie eine RPC-Dienstschnittstelle in einer .proto-Datei definieren, und der Protokollpuffer-Compiler generiert Dienstschnittstellen-Code und Stubs in Ihrer gewählten Sprache. Wenn Sie beispielsweise einen RPC-Dienst mit einer Methode definieren möchten, die Ihre SearchRequest entgegennimmt und eine SearchResponse zurückgibt, können Sie ihn wie folgt in Ihrer .proto-Datei definieren
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
Das einfachste RPC-System, das mit Protocol Buffers verwendet werden kann, ist gRPC: ein sprach- und plattformunabhängiges Open-Source-RPC-System, das bei Google entwickelt wurde. gRPC funktioniert besonders gut mit Protocol Buffers und ermöglicht es Ihnen, den relevanten RPC-Code direkt aus Ihren .proto-Dateien mithilfe eines speziellen Protocol Buffer-Compiler-Plugins zu generieren.
Wenn Sie gRPC nicht verwenden möchten, ist es auch möglich, Protocol Buffers mit Ihrer eigenen RPC-Implementierung zu verwenden. Weitere Informationen hierzu finden Sie im Proto2 Sprachleitfaden.
Es gibt auch eine Reihe von laufenden Drittanbieterprojekten zur Entwicklung von RPC-Implementierungen für Protocol Buffers. Eine Liste von Links zu Projekten, die uns bekannt sind, finden Sie auf der Wiki-Seite Add-ons von Drittanbietern.
JSON-Mapping
Das Standard-Binärformat von Protobuf ist das bevorzugte Serialisierungsformat für die Kommunikation zwischen zwei Systemen, die Protobufs verwenden. Für die Kommunikation mit Systemen, die JSON anstelle des Protobuf-Binärformats verwenden, unterstützt Protobuf eine kanonische Kodierung in JSON.
Optionen
Einzelne Deklarationen in einer .proto-Datei können mit einer Reihe von Optionen annotiert werden. Optionen ändern nicht die Gesamtbedeutung einer Deklaration, können aber beeinflussen, wie sie in einem bestimmten Kontext behandelt wird. Die vollständige Liste der verfügbaren Optionen ist in /google/protobuf/descriptor.proto definiert.
Einige Optionen sind Dateiebene-Optionen, d. h. sie sollten im obersten Geltungsbereich geschrieben werden, nicht innerhalb von Nachrichten-, Enum- oder Dienstdefinitionen. Einige Optionen sind Nachrichtenebene-Optionen, d. h. sie sollten innerhalb von Nachrichtendefinitionen geschrieben werden. Einige Optionen sind Feldebene-Optionen, d. h. sie sollten innerhalb von Felddefinitionen geschrieben werden. Optionen können auch für Enum-Typen, Enum-Werte, Oneof-Felder, Diensttypen und Dienstmethoden geschrieben werden; derzeit gibt es jedoch keine nützlichen Optionen für diese.
Hier sind einige der am häufigsten verwendeten Optionen
java_package(Date Option): Das Paket, das Sie für Ihre generierten Java/Kotlin-Klassen verwenden möchten. Wenn keine explizitejava_package-Option in der.proto-Datei angegeben ist, wird standardmäßig das Proto-Paket (angegeben mit dem Schlüsselwort „package“ in der.proto-Datei) verwendet. Proto-Pakete eignen sich jedoch im Allgemeinen nicht gut als Java-Pakete, da Proto-Pakete nicht mit umgekehrten Domainnamen beginnen müssen. Wenn kein Java- oder Kotlin-Code generiert wird, hat diese Option keine Auswirkung.option java_package = "com.example.foo";java_outer_classname(File Option): Der Klassenname (und damit der Dateiname) für die Wrapper-Java-Klasse, die Sie generieren möchten. Wenn keine explizitejava_outer_classname-Option in der.proto-Datei angegeben ist, wird der Klassenname durch Konvertierung des.proto-Dateinamens in Camelcase gebildet (so wirdfoo_bar.protozuFooBar.java). Wenn die Optionjava_multiple_filesdeaktiviert ist, werden alle anderen Klassen/Enums/etc., die für die.proto-Datei generiert werden, **innerhalb** dieser äußeren Wrapper-Java-Klasse als verschachtelte Klassen/Enums/etc. generiert. Wenn kein Java-Code generiert wird, hat diese Option keine Auswirkung.option java_outer_classname = "Ponycopter";java_multiple_files(Dateioption): Wenn false, wird nur eine einzelne.java-Datei für diese.proto-Datei generiert, und alle Java-Klassen/Enums/usw., die für die Top-Level-Nachrichten, Dienste und Aufzählungen generiert werden, werden in einer äußeren Klasse verschachtelt (siehejava_outer_classname). Wenn true, werden separate.java-Dateien für jede der Java-Klassen/Enums/usw. generiert, die für die Top-Level-Nachrichten, Dienste und Aufzählungen generiert werden, und die Wrapper-Java-Klasse, die für diese.proto-Datei generiert wird, enthält keine verschachtelten Klassen/Enums/usw. Dies ist eine Boolesche Option, die standardmäßigfalseist. Wenn kein Java-Code generiert wird, hat diese Option keine Auswirkung.option java_multiple_files = true;optimize_for(Datei-Option): Kann aufSPEED,CODE_SIZEoderLITE_RUNTIMEgesetzt werden. Dies beeinflusst die C++- und Java-Code-Generatoren (und möglicherweise Drittanbieter-Generatoren) wie folgt:SPEED(Standard): Der Protocol-Buffer-Compiler generiert Code für die Serialisierung, das Parsen und die Ausführung anderer gängiger Operationen mit Ihren Nachrichtentypen. Dieser Code ist hochoptimiert.CODE_SIZE: Der Protocol-Buffer-Compiler generiert minimale Klassen und verlässt sich auf gemeinsam genutzten, reflexionsbasierten Code zur Implementierung von Serialisierung, Parsing und verschiedenen anderen Operationen. Der generierte Code wird dadurch viel kleiner sein als beiSPEED, aber die Operationen werden langsamer sein. Klassen implementieren weiterhin genau die gleiche öffentliche API wie imSPEED-Modus. Dieser Modus ist am nützlichsten in Apps, die eine sehr große Anzahl von.proto-Dateien enthalten und nicht alle davon blitzschnell benötigen.LITE_RUNTIME: Der Protocol-Buffer-Compiler generiert Klassen, die nur von der „Lite“-Runtime-Bibliothek (libprotobuf-liteanstelle vonlibprotobuf) abhängen. Die Lite-Runtime ist viel kleiner als die vollständige Bibliothek (ungefähr um eine Größenordnung kleiner), aber sie lässt bestimmte Funktionen wie Deskriptoren und Reflexion weg. Dies ist besonders nützlich für Apps, die auf eingeschränkten Plattformen wie Mobiltelefonen laufen. Der Compiler generiert weiterhin schnelle Implementierungen aller Methoden, wie er es imSPEED-Modus tut. Generierte Klassen implementieren nur dieMessageLite-Schnittstelle in jeder Sprache, die nur eine Teilmenge der Methoden der vollständigenMessage-Schnittstelle bereitstellt.
option optimize_for = CODE_SIZE;cc_generic_services,java_generic_services,py_generic_services(Datei-Optionen): Generische Dienste sind veraltet. Ob der Protocol-Buffer-Compiler abstrakten Dienstcode basierend auf Dienstdefinitionen in C++, Java bzw. Python generieren soll. Aus Kompatibilitätsgründen sind diese standardmäßig auftruegesetzt. Seit Version 2.3.0 (Januar 2010) wird es jedoch als vorzuziehen angesehen, dass RPC-Implementierungen Code-Generator-Plugins bereitstellen, um Code zu generieren, der für jedes System spezifischer ist, anstatt sich auf die „abstrakten“ Dienste zu verlassen.// This file relies on plugins to generate service code. option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false;cc_enable_arenas(Datei-Option): Aktiviert die Arena-Speicherverwaltung für C++-generierten Code.objc_class_prefix(Datei-Option): Legt das Objective-C-Klassenpräfix fest, das allen von diesem .proto generierten Objective-C-Klassen und Enums vorangestellt wird. Es gibt keinen Standardwert. Sie sollten Präfixe zwischen 3-5 Großbuchstaben verwenden, wie von Apple empfohlen. Beachten Sie, dass alle 2-Buchstaben-Präfixe von Apple reserviert sind.packed(Feldoption): Standardmäßigtruebei einem wiederholten Feld eines grundlegenden numerischen Typs, was zu einer kompakteren Kodierung führt. Um das ungepackte Wireformat zu verwenden, kann es auffalsegesetzt werden. Dies bietet Kompatibilität mit Parsen vor Version 2.3.0 (selten benötigt), wie im folgenden Beispiel gezeigtrepeated int32 samples = 4 [packed = false];deprecated(Feldoption): Wenn auftruegesetzt, zeigt dies an, dass das Feld veraltet ist und von neuem Code nicht verwendet werden sollte. In den meisten Sprachen hat dies keine tatsächliche Auswirkung. In Java wird dies zu einer@Deprecated-Annotation. Für C++ generiert clang-tidy Warnungen, wenn veraltete Felder verwendet werden. In Zukunft können andere sprachspezifische Code-Generatoren Deprecations-Annotationen für die Accessoren des Feldes generieren, was wiederum eine Warnung auslöst, wenn Code kompiliert wird, der versucht, das Feld zu verwenden. Wenn das Feld von niemandem verwendet wird und Sie verhindern möchten, dass neue Benutzer es verwenden, sollten Sie die Felddeklaration durch eine reservierte Anweisung ersetzen.int32 old_field = 6 [deprecated = true];
Optionen für Enum-Werte
Enum-Wert-Optionen werden unterstützt. Sie können die Option deprecated verwenden, um anzuzeigen, dass ein Wert nicht mehr verwendet werden sollte. Sie können auch benutzerdefinierte Optionen mithilfe von Erweiterungen erstellen.
Das folgende Beispiel zeigt die Syntax zum Hinzufügen dieser Optionen.
import "google/protobuf/descriptor.proto";
extend google.protobuf.EnumValueOptions {
optional string string_name = 123456789;
}
enum Data {
DATA_UNSPECIFIED = 0;
DATA_SEARCH = 1 [deprecated = true];
DATA_DISPLAY = 2 [
(string_name) = "display_value"
];
}
Der C++-Code zum Lesen der Option string_name könnte ungefähr so aussehen:
const absl::string_view foo = proto2::GetEnumDescriptor<Data>()
->FindValueByName("DATA_DISPLAY")->options().GetExtension(string_name);
Siehe Benutzerdefinierte Optionen, um zu erfahren, wie benutzerdefinierte Optionen auf Enum-Werte und Felder angewendet werden.
Benutzerdefinierte Optionen
Protocol Buffers erlaubt es Ihnen auch, eigene Optionen zu definieren und zu verwenden. Beachten Sie, dass dies ein erweitertes Feature ist, das die meisten Leute nicht benötigen. Wenn Sie der Meinung sind, dass Sie eigene Optionen erstellen müssen, finden Sie Details im Proto2 Sprachleitfaden. Beachten Sie, dass das Erstellen von benutzerdefinierten Optionen Erweiterungen verwendet, die in proto3 nur für benutzerdefinierte Optionen zulässig sind.
Option-Aufbewahrung
Optionen haben eine Vorstellung von Aufbewahrung (retention), die steuert, ob eine Option im generierten Code beibehalten wird. Optionen haben standardmäßig Laufzeitaufbewahrung (runtime retention), was bedeutet, dass sie im generierten Code beibehalten werden und somit zur Laufzeit im generierten Deskriptorpool sichtbar sind. Sie können jedoch retention = RETENTION_SOURCE setzen, um anzugeben, dass eine Option (oder ein Feld innerhalb einer Option) zur Laufzeit nicht beibehalten werden muss. Dies wird als Source Retention bezeichnet.
Die Aufbewahrung von Optionen ist ein fortgeschrittenes Feature, um das sich die meisten Benutzer keine Sorgen machen müssen, aber es kann nützlich sein, wenn Sie bestimmte Optionen verwenden möchten, ohne die Code-Größenkosten für die Beibehaltung in Ihren Binärdateien zu zahlen. Optionen mit Source Retention sind für protoc und protoc Plugins weiterhin sichtbar, sodass Code-Generatoren sie zur Anpassung ihres Verhaltens verwenden können.
Die Aufbewahrung kann direkt auf einer Option gesetzt werden, so:
extend google.protobuf.FileOptions {
optional int32 source_retention_option = 1234
[retention = RETENTION_SOURCE];
}
Sie kann auch auf einem einfachen Feld gesetzt werden, in diesem Fall tritt sie nur in Kraft, wenn dieses Feld innerhalb einer Option vorkommt.
message OptionsMessage {
int32 source_retention_field = 1 [retention = RETENTION_SOURCE];
}
Sie können gerne retention = RETENTION_RUNTIME setzen, aber dies hat keine Auswirkung, da es das Standardverhalten ist. Wenn ein Nachrichtenfeld als RETENTION_SOURCE markiert ist, werden seine gesamten Inhalte verworfen; Felder darin können dies nicht überschreiben, indem sie versuchen, RETENTION_RUNTIME zu setzen.
Hinweis
Ab Protocol Buffers 22.0 ist die Unterstützung für Optionsaufbewahrung noch in Arbeit und nur C++ und Java werden unterstützt. Go hat Unterstützung ab Version 1.29.0. Die Python-Unterstützung ist vollständig, hat es aber noch nicht in eine Veröffentlichung geschafft.Option-Ziele
Felder haben eine targets-Option, die die Arten von Entitäten steuert, auf die das Feld angewendet werden kann, wenn es als Option verwendet wird. Wenn ein Feld beispielsweise targets = TARGET_TYPE_MESSAGE hat, kann dieses Feld nicht in einer benutzerdefinierten Option auf einer Aufzählung (oder einer anderen Nicht-Nachrichten-Entität) gesetzt werden. Protoc erzwingt dies und löst einen Fehler aus, wenn die Zielbeschränkungen verletzt werden.
Auf den ersten Blick mag dieses Feature unnötig erscheinen, da jede benutzerdefinierte Option eine Erweiterung der Optionsnachricht für eine bestimmte Entität ist, was die Option bereits auf diese eine Entität beschränkt. Allerdings sind Optionsziele nützlich, wenn Sie eine gemeinsam genutzte Optionsnachricht haben, die auf mehrere Entitätstypen angewendet wird, und Sie die Verwendung einzelner Felder in dieser Nachricht steuern möchten. Zum Beispiel:
message MyOptions {
string file_only_option = 1 [targets = TARGET_TYPE_FILE];
int32 message_and_enum_option = 2 [targets = TARGET_TYPE_MESSAGE,
targets = TARGET_TYPE_ENUM];
}
extend google.protobuf.FileOptions {
optional MyOptions file_options = 50000;
}
extend google.protobuf.MessageOptions {
optional MyOptions message_options = 50000;
}
extend google.protobuf.EnumOptions {
optional MyOptions enum_options = 50000;
}
// OK: this field is allowed on file options
option (file_options).file_only_option = "abc";
message MyMessage {
// OK: this field is allowed on both message and enum options
option (message_options).message_and_enum_option = 42;
}
enum MyEnum {
MY_ENUM_UNSPECIFIED = 0;
// Error: file_only_option cannot be set on an enum.
option (enum_options).file_only_option = "xyz";
}
Generieren Ihrer Klassen
Um den Java-, Kotlin-, Python-, C++-, Go-, Ruby-, Objective-C- oder C#-Code zu generieren, den Sie zum Arbeiten mit den Nachrichtentypen in einer .proto-Datei benötigen, müssen Sie den Protocol-Buffer-Compiler protoc auf die .proto-Datei anwenden. Wenn Sie den Compiler noch nicht installiert haben, laden Sie das Paket herunter und befolgen Sie die Anweisungen in der README. Für Go müssen Sie außerdem ein spezielles Code-Generator-Plugin für den Compiler installieren; dieses und die Installationsanweisungen finden Sie im golang/protobuf Repository auf GitHub.
Der Protobuf-Compiler wird wie folgt aufgerufen
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
IMPORT_PATHgibt ein Verzeichnis an, in dem nach.proto-Dateien gesucht werden soll, wennimport-Direktiven aufgelöst werden. Wenn weggelassen, wird das aktuelle Verzeichnis verwendet. Mehrere Importverzeichnisse können durch mehrmaliges Übergeben der Option--proto_pathangegeben werden.-I=_IMPORT_PATH_kann als Kurzform von--proto_pathverwendet werden.
Hinweis: Dateipfade relativ zu ihrem proto_path müssen in einer bestimmten Binärdatei global eindeutig sein. Wenn Sie beispielsweise proto/lib1/data.proto und proto/lib2/data.proto haben, können diese beiden Dateien nicht zusammen mit -I=proto/lib1 -I=proto/lib2 verwendet werden, da es mehrdeutig wäre, welche Datei import "data.proto" bedeutet. Stattdessen sollte -Iproto/ verwendet werden und die globalen Namen werden lib1/data.proto und lib2/data.proto sein.
Wenn Sie eine Bibliothek veröffentlichen und andere Benutzer Ihre Nachrichten direkt verwenden dürfen, sollten Sie einen eindeutigen Bibliotheksnamen in den Pfad aufnehmen, unter dem sie voraussichtlich verwendet werden, um Namenskollisionen zu vermeiden. Wenn Sie mehrere Verzeichnisse in einem Projekt haben, ist es bewährte Praxis, einen einzigen -I auf ein übergeordnetes Verzeichnis des Projekts zu setzen.
Sie können eine oder mehrere Ausgaberichtlinien angeben:
--cpp_outgeneriert C++-Code inDST_DIR. Mehr dazu finden Sie in der Referenz für generierten C++-Code.--java_outgeneriert Java-Code inDST_DIR. Mehr dazu finden Sie in der Referenz für generierten Java-Code.--kotlin_outgeneriert zusätzlichen Kotlin-Code inDST_DIR. Mehr dazu finden Sie in der Referenz für generierten Kotlin-Code.--python_outgeneriert Python-Code inDST_DIR. Mehr dazu finden Sie in der Referenz für generierten Python-Code.--go_outgeneriert Go-Code inDST_DIR. Mehr dazu finden Sie in der Referenz für generierten Go-Code.--ruby_outgeneriert Ruby-Code inDST_DIR. Mehr dazu finden Sie in der Referenz für generierten Ruby-Code.--objc_outgeneriert Objective-C-Code inDST_DIR. Mehr dazu finden Sie in der Referenz für generierten Objective-C-Code.--csharp_outgeneriert C#-Code inDST_DIR. Mehr dazu finden Sie in der Referenz für generierten C#-Code.--php_outgeneriert PHP-Code inDST_DIR. Mehr dazu finden Sie in der Referenz für generierten PHP-Code.
Als zusätzlichen Komfort, wenn die
DST_DIRmit.zipoder.jarendet, schreibt der Compiler die Ausgabe in eine einzelne ZIP-formatierte Archivdatei mit dem angegebenen Namen..jar-Ausgaben erhalten auch eine Manifestdatei gemäß den Anforderungen der Java JAR-Spezifikation. Beachten Sie, dass das Archiv überschrieben wird, wenn es bereits existiert.Sie müssen eine oder mehrere
.proto-Dateien als Eingabe angeben. Mehrere.proto-Dateien können gleichzeitig angegeben werden. Obwohl die Dateien relativ zum aktuellen Verzeichnis benannt sind, muss jede Datei in einem derIMPORT_PATHs liegen, damit der Compiler ihren kanonischen Namen ermitteln kann.
Dateiposition
Es ist ratsam, .proto-Dateien nicht im selben Verzeichnis wie andere Sprachquellen abzulegen. Erwägen Sie die Erstellung eines Unterpakets proto für .proto-Dateien unter dem Stammverzeichnis Ihres Projekts.
Position sollte sprachunabhängig sein
Beim Arbeiten mit Java-Code ist es praktisch, zusammengehörige .proto-Dateien im selben Verzeichnis wie den Java-Quellcode abzulegen. Wenn jedoch Nicht-Java-Code jemals dieselben Protos verwendet, ist das Pfadpräfix nicht mehr sinnvoll. Legen Sie daher im Allgemeinen die Protos in ein sprachunabhängiges Verzeichnis, z. B. //myteam/mypackage.
Die Ausnahme von dieser Regel ist, wenn klar ist, dass die Protos nur in einem Java-Kontext verwendet werden, z. B. zum Testen.
Unterstützte Plattformen
Für Informationen über
- die unterstützten Betriebssysteme, Compiler, Build-Systeme und C++-Versionen siehe Grundlegende C++-Support-Richtlinie.
- die unterstützten PHP-Versionen siehe Unterstützte PHP-Versionen.