Java Generated Code Guide
Alle Unterschiede zwischen den von proto2, proto3 und Editions generierten Codes werden hervorgehoben – beachten Sie, dass sich diese Unterschiede im generierten Code befinden, wie in diesem Dokument beschrieben, nicht in den Basisnachrichtenklassen/-schnittstellen, die in allen Versionen gleich sind. Sie sollten die Sprachleitfäden für proto2, proto3 und/oder Editions lesen, bevor Sie dieses Dokument lesen.
Beachten Sie, dass keine Java-Protokollpuffer-Methoden Nullwerte akzeptieren oder zurückgeben, sofern nicht anders angegeben.
Compiler-Aufruf
Der Protokollpuffer-Compiler erzeugt Java-Ausgaben, wenn er mit dem Befehlszeilenflag --java_out= aufgerufen wird. Der Parameter der Option --java_out= ist das Verzeichnis, in das der Compiler Ihre Java-Ausgaben schreiben soll. Für jede .proto-Eingabedatei erstellt der Compiler eine Wrapper-.java-Datei, die eine Java-Klasse enthält, die die .proto-Datei selbst darstellt.
Wenn die .proto-Datei eine Zeile wie die folgende enthält
option java_multiple_files = true;
Dann erstellt der Compiler auch separate .java-Dateien für jede der Klassen/Enums, die er für jede Top-Level-Nachricht, Enumeration und jeden Dienst generieren wird, die in der .proto-Datei deklariert sind.
Andernfalls (wenn die Option java_multiple_files deaktiviert ist, was der Standard ist), wird die oben erwähnte Wrapper-Klasse auch als äußere Klasse verwendet, und die generierten Klassen/Enums für jede Top-Level-Nachricht, Enumeration und jeden Dienst, die in der .proto-Datei deklariert sind, werden alle innerhalb der äußeren Wrapper-Klasse verschachtelt. Somit generiert der Compiler nur eine einzige .java-Datei für die gesamte .proto-Datei, und sie hat eine zusätzliche Ebene im Paket
Der Name der Wrapper-Klasse wird wie folgt gewählt: Wenn die .proto-Datei eine Zeile wie die folgende enthält
option java_outer_classname = "Foo";
Dann ist der Klassenname des Wrappers Foo. Andernfalls wird der Klassenname des Wrappers durch Umwandlung des Basisnamens der .proto-Datei in Camel-Case bestimmt. Zum Beispiel wird foo_bar.proto einen Klassennamen von FooBar generieren. Wenn es einen Dienst, ein Enum oder eine Nachricht (einschließlich verschachtelter Typen) in der Datei mit demselben Namen gibt, wird "OuterClass" an den Klassennamen des Wrappers angehängt. Beispiele
- Wenn
foo_bar.protoeine Nachricht namensFooBarenthält, generiert die Wrapper-Klasse einen Klassennamen vonFooBarOuterClass. - Wenn
foo_bar.protoeinen Dienst namensFooServiceenthält undjava_outer_classnameebenfalls auf den StringFooServicegesetzt ist, generiert die Wrapper-Klasse einen Klassennamen vonFooServiceOuterClass.
Hinweis
Wenn Sie die veraltete v1 der Protobuf-API verwenden, wirdOuterClass unabhängig von Kollisionen mit Nachrichtennamen hinzugefügt.Zusätzlich zu allen verschachtelten Klassen verfügt die Wrapper-Klasse selbst über die folgende API (angenommen, die Wrapper-Klasse heißt Foo und wurde aus foo.proto generiert)
public final class Foo {
private Foo() {} // Not instantiable.
/** Returns a FileDescriptor message describing the contents of {@code foo.proto}. */
public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor();
/** Adds all extensions defined in {@code foo.proto} to the given registry. */
public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry);
public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry);
// (Nested classes omitted)
}
Der Java-Paketname wird wie unter Packages unten beschrieben gewählt.
Die Ausgabedatei wird durch Verketten des Parameters von --java_out=, des Paketnamens (mit ersetzten . durch /) und des .java-Dateinamens gewählt.
Nehmen wir also zum Beispiel an, Sie rufen den Compiler wie folgt auf:
protoc --proto_path=src --java_out=build/gen src/foo.proto
Wenn das Java-Paket von foo.proto com.example ist und java_multiple_files nicht aktiviert ist und sein outer classname FooProtos ist, generiert der Protokollpuffer-Compiler die Datei build/gen/com/example/FooProtos.java. Der Protokollpuffer-Compiler erstellt automatisch die Verzeichnisse build/gen/com und build/gen/com/example, falls erforderlich. Er erstellt jedoch nicht build/gen oder build; diese müssen bereits vorhanden sein. Sie können mehrere .proto-Dateien in einem einzigen Aufruf angeben; alle Ausgabedateien werden gleichzeitig generiert.
Bei der Ausgabe von Java-Code ist die Fähigkeit des Protokollpuffer-Compilers, direkt in JAR-Archive auszugeben, besonders praktisch, da viele Java-Tools Quellcode direkt aus JAR-Dateien lesen können. Um in eine JAR-Datei auszugeben, geben Sie einfach einen Ausgabespeicherort an, der auf .jar endet. Beachten Sie, dass nur der Java-Quellcode in das Archiv aufgenommen wird; Sie müssen ihn immer noch separat kompilieren, um Java-Klassendateien zu erzeugen.
Pakete (Packages)
Die generierte Klasse wird in einem Java-Paket basierend auf der Option java_package platziert. Wenn die Option weggelassen wird, wird stattdessen die Deklaration package verwendet.
Wenn beispielsweise die .proto-Datei Folgendes enthält
package foo.bar;
Dann wird die resultierende Java-Klasse im Java-Paket foo.bar platziert. Wenn die .proto-Datei jedoch auch eine Option java_package enthält, wie folgt
package foo.bar;
option java_package = "com.example.foo.bar";
Dann wird die Klasse stattdessen im Paket com.example.foo.bar platziert. Die Option java_package wird bereitgestellt, da normale .proto-package-Deklarationen nicht mit einem umgekehrten Domainnamen beginnen sollen.
Nachrichten
Wenn Sie ein neues Protokollpuffer-Schema entwerfen, siehe die Empfehlungen für Java Proto-Namen.
Angesichts einer einfachen Nachrichten Deklaration:
message Foo {}
Der Protokollpuffer-Compiler generiert eine Klasse namens Foo, die die Schnittstelle Message implementiert. Die Klasse wird als final deklariert; keine weitere Unterklassifizierung ist erlaubt. Foo erweitert GeneratedMessage, dies sollte jedoch als Implementierungsdetail betrachtet werden. Standardmäßig überschreibt Foo viele Methoden von GeneratedMessage mit spezialisierten Versionen für maximale Geschwindigkeit. Wenn die .proto-Datei jedoch die Zeile enthält
option optimize_for = CODE_SIZE;
Dann überschreibt Foo nur die minimale Menge an notwendigen Methoden zur Funktion und verlässt sich auf die reflektionsbasierten Implementierungen von GeneratedMessage für die restlichen. Dies reduziert die Größe des generierten Codes erheblich, verringert aber auch die Leistung. Alternativ, wenn die .proto-Datei enthält
option optimize_for = LITE_RUNTIME;
Dann enthält Foo schnelle Implementierungen aller Methoden, implementiert aber die Schnittstelle MessageLite, die eine Teilmenge der Methoden von Message enthält. Insbesondere werden keine Deskriptoren, verschachtelten Builder oder Reflexion unterstützt. In diesem Modus muss der generierte Code jedoch nur gegen libprotobuf-lite.jar statt gegen libprotobuf.jar verlinkt werden. Die "lite"-Bibliothek ist viel kleiner als die vollständige Bibliothek und eignet sich besser für ressourcenbeschränkte Systeme wie Mobiltelefone.
Die Schnittstelle Message definiert Methoden, mit denen Sie die gesamte Nachricht prüfen, manipulieren, lesen oder schreiben können. Zusätzlich zu diesen Methoden definiert die Klasse Foo die folgenden statischen Methoden
static Foo getDefaultInstance(): Gibt die *Singleton*-Instanz vonFoozurück. Der Inhalt dieser Instanz ist identisch mit dem, was Sie erhalten würden, wenn SieFoo.newBuilder().build()aufrufen würden (sodass alle singulären Felder auf nicht gesetzt und alle wiederholten Felder leer sind). Beachten Sie, dass die Standardinstanz einer Nachricht als Fabrik verwendet werden kann, indem ihre MethodenewBuilderForType()aufgerufen wird.static Descriptor getDescriptor(): Gibt den Deskriptor des Typs zurück. Dieser enthält Informationen über den Typ, einschließlich seiner Felder und deren Typen. Dies kann mit den Reflexionsmethoden vonMessagewiegetField()verwendet werden.static Foo parseFrom(...): Parst eine Nachricht vom TypFooaus der gegebenen Quelle und gibt sie zurück. Für jede Variante vonmergeFrom()in der SchnittstelleMessage.Buildergibt es eine entsprechendeparseFrom-Methode. Beachten Sie, dassparseFrom()niemalsUninitializedMessageExceptionwirft; es wirftInvalidProtocolBufferException, wenn die geparste Nachricht erforderliche Felder fehlen. Dies macht es subtil anders als der Aufruf vonFoo.newBuilder().mergeFrom(...).build().static Parser parser(): Gibt eine Instanz desParserzurück, der verschiedeneparseFrom()-Methoden implementiert.Foo.Builder newBuilder(): Erstellt einen neuen Builder (wird unten beschrieben).Foo.Builder newBuilder(Foo prototype): Erstellt einen neuen Builder, bei dem alle Felder mit denselben Werten initialisiert sind, die sie inprototypehaben. Da eingebettete Nachrichten- und Zeichenkettenobjekte unveränderlich sind, werden sie zwischen Original und Kopie geteilt.
Builder
Nachrichtenobjekte – wie Instanzen der oben beschriebenen Klasse Foo – sind unveränderlich, genau wie eine Java String. Um ein Nachrichtenobjekt zu konstruieren, müssen Sie einen *Builder* verwenden. Jede Nachrichtenklasse hat ihre eigene Builder-Klasse – in unserem Foo-Beispiel generiert der Protokollpuffer-Compiler eine verschachtelte Klasse Foo.Builder, die zum Erstellen eines Foo verwendet werden kann. Foo.Builder implementiert die Schnittstelle Message.Builder. Sie erweitert die Klasse GeneratedMessage.Builder, aber auch dies sollte als Implementierungsdetail betrachtet werden. Wie Foo kann Foo.Builder auf generische Implementierungen in GeneratedMessage.Builder zurückgreifen oder, wenn die Option optimize_for verwendet wird, generierten benutzerdefinierten Code, der viel schneller ist. Sie können einen Foo.Builder erhalten, indem Sie die statische Methode Foo.newBuilder() aufrufen.
Foo.Builder definiert keine statischen Methoden. Seine Schnittstelle ist genau wie von der Schnittstelle Message.Builder definiert, mit der Ausnahme, dass die Rückgabetypen spezifischer sind: Methoden von Foo.Builder, die den Builder modifizieren, geben den Typ Foo.Builder zurück, und build() gibt den Typ Foo zurück.
Methoden, die den Inhalt eines Builders ändern – einschließlich Feldsettern – geben immer eine Referenz auf den Builder zurück (d. h. sie „return this;“). Dies ermöglicht die Verkettung mehrerer Methodenaufrufe in einer Zeile. Zum Beispiel: builder.mergeFrom(obj).setFoo(1).setBar("abc").clearBaz();
Beachten Sie, dass Builder nicht threadsicher sind, daher sollte Java-Synchronisation verwendet werden, wenn es für mehrere verschiedene Threads notwendig ist, den Inhalt eines einzelnen Builders zu modifizieren.
Unter-Builder
Für Nachrichten, die Unternachrichten enthalten, generiert der Compiler auch Unter-Builder. Dies ermöglicht es Ihnen, tief verschachtelte Unternachrichten wiederholt zu modifizieren, ohne sie neu zu erstellen. Zum Beispiel
message Foo {
int32 val = 1;
// some other fields.
}
message Bar {
Foo foo = 1;
// some other fields.
}
message Baz {
Bar bar = 1;
// some other fields.
}
Wenn Sie bereits eine Baz-Nachricht haben und den tief verschachtelten val in Foo ändern möchten. Anstatt
baz = baz.toBuilder().setBar(
baz.getBar().toBuilder().setFoo(
baz.getBar().getFoo().toBuilder().setVal(10).build()
).build()).build();
Sie können schreiben
Baz.Builder builder = baz.toBuilder();
builder.getBarBuilder().getFooBuilder().setVal(10);
baz = builder.build();
Verschachtelte Typen
Eine Nachricht kann innerhalb einer anderen Nachricht deklariert werden. Zum Beispiel:
message Foo {
message Bar { }
}
In diesem Fall generiert der Compiler einfach Bar als innere Klasse, die in Foo verschachtelt ist.
Felder
Zusätzlich zu den in den vorherigen Abschnitten beschriebenen Methoden generiert der Protokollpuffer-Compiler eine Reihe von Zugriffsmethoden für jedes Feld, das in der Nachricht in der .proto-Datei definiert ist. Die Methoden, die den Feldwert lesen, sind sowohl in der Nachrichtenklasse als auch in ihrem entsprechenden Builder definiert; die Methoden, die den Wert ändern, sind nur im Builder definiert.
Beachten Sie, dass Methodennamen immer Camel-Case-Namensgebung verwenden, auch wenn der Feldname in der .proto-Datei Kleinbuchstaben mit Unterstrichen verwendet (wie es sein sollte). Die Fallumwandlung funktioniert wie folgt
- Für jeden Unterstrich im Namen wird der Unterstrich entfernt und der folgende Buchstabe großgeschrieben.
- Wenn dem Namen ein Präfix (z. B. "get") vorangestellt wird, wird der erste Buchstabe großgeschrieben. Andernfalls wird er kleingeschrieben.
- Der Buchstabe nach jeder Ziffer in einer Zahl im Methodennamen wird großgeschrieben.
Somit wird das Feld foo_bar_baz zu fooBarBaz. Wenn es mit get präfixiert wäre, wäre es getFooBarBaz. Und foo_ba23r_baz wird zu fooBa23RBaz.
Zusätzlich zu den Zugriffsmethoden generiert der Compiler eine Ganzzahlkonstante für jedes Feld, die seine Feldnummer enthält. Der Konstantenname ist der Feldname, umgewandelt in Großbuchstaben, gefolgt von _FIELD_NUMBER. Zum Beispiel wird für das Feld int32 foo_bar = 5; vom Compiler die Konstante public static final int FOO_BAR_FIELD_NUMBER = 5; generiert.
Die folgenden Abschnitte sind in explizite und implizite Anwesenheit unterteilt. Proto2 hat explizite Anwesenheit und proto3 hat standardmäßig implizite Anwesenheit. Editions haben standardmäßig explizite Anwesenheit, aber Sie können dies mit features.field_presence überschreiben.
Singuläre Felder mit expliziter Anwesenheit
Für jede dieser Felddefinitionen
optional int32 foo = 1;
required int32 foo = 1;
Der Compiler generiert die folgenden Zugriffsmethoden sowohl in der Nachrichtenklasse als auch in ihrem Builder
boolean hasFoo(): Gibttruezurück, wenn das Feld gesetzt ist.int getFoo(): Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, gibt es den Standardwert zurück.
Der Compiler generiert die folgenden Methoden nur im Builder der Nachricht
Builder setFoo(int value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibthasFoo()truezurück undgetFoo()gibtvaluezurück.Builder clearFoo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibthasFoo()falsezurück undgetFoo()gibt den Standardwert zurück.
Für andere einfache Feldtypen wird der entsprechende Java-Typ gemäß der Tabelle der Skalarwerttypen gewählt. Für Nachrichten- und Enum-Typen wird der Werttyp durch die Nachrichten- oder Enum-Klasse ersetzt.
Eingebettete Nachrichtenfelder
Für Nachrichtentypen akzeptiert setFoo() auch eine Instanz des Builder-Typs der Nachricht als Parameter. Dies ist nur eine Abkürzung, die dem Aufruf von .build() auf dem Builder und der Übergabe des Ergebnisses an die Methode entspricht. Weitere Modifikationen des Unter-Builders, der an setFoo übergeben wird, werden sich **nicht** auf den Builder der Nachrichtenklasse auswirken. Der Builder der Nachrichtenklasse "übernimmt den Besitz" der Unter-Nachricht.
Wenn das Feld nicht gesetzt ist, gibt getFoo() eine Foo-Instanz zurück, bei der keine Felder gesetzt sind (möglicherweise die Instanz, die von Foo.getDefaultInstance() zurückgegeben wird).
Zusätzlich generiert der Compiler zwei Zugriffsmethoden, die es Ihnen ermöglichen, auf die relevanten Unter-Builder für Nachrichtentypen zuzugreifen. Die folgende Methode wird sowohl in der Nachrichtenklasse als auch in ihrem Builder generiert
FooOrBuilder getFooOrBuilder(): Gibt den Builder für das Feld zurück, wenn er bereits existiert, oder die Nachricht, wenn nicht. Das Aufrufen dieser Methode auf Builder erstellt keinen Unter-Builder für das Feld.
Der Compiler generiert die folgende Methode nur im Builder der Nachricht.
Builder getFooBuilder(): Gibt den Builder für das Feld zurück.
Singuläre Felder mit impliziter Anwesenheit
Für diese Felddefinition:
int32 foo = 1;
Der Compiler generiert die folgende Zugriffsmethode sowohl in der Nachrichtenklasse als auch in ihrem Builder
int getFoo(): Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, gibt es den Standardwert für den Typ des Feldes zurück.
Der Compiler generiert die folgenden Methoden nur im Builder der Nachricht
Builder setFoo(int value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibtgetFoo()valuezurück.Builder clearFoo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibtgetFoo()den Standardwert für den Typ des Feldes zurück.
Für andere einfache Feldtypen wird der entsprechende Java-Typ gemäß der Tabelle der Skalarwerttypen gewählt. Für Nachrichten- und Enum-Typen wird der Werttyp durch die Nachrichten- oder Enum-Klasse ersetzt.
Enum-Felder
Für Enum-Feldtypen wird eine zusätzliche Zugriffsmethode sowohl in der Nachrichtenklasse als auch in ihrem Builder generiert
int getFooValue(): Gibt den Ganzzahlwert des Enums zurück.
Der Compiler generiert die folgende zusätzliche Methode nur im Builder der Nachricht
Builder setFooValue(int value): Setzt den Ganzzahlwert des Enums.
Zusätzlich gibt getFoo() UNRECOGNIZED zurück, wenn der Enum-Wert unbekannt ist – dies ist ein spezieller zusätzlicher Wert, der vom proto3-Compiler zum generierten Enum-Typ hinzugefügt wird.
Wiederholte Felder
Für diese Felddefinition:
repeated string foos = 1;
Der Compiler generiert die folgenden Zugriffsmethoden sowohl in der Nachrichtenklasse als auch in ihrem Builder
int getFoosCount(): Gibt die aktuelle Anzahl der Elemente im Feld zurück.String getFoos(int index): Gibt das Element am gegebenen nullbasierten Index zurück.ProtocolStringList getFoosList(): Gibt das gesamte Feld alsProtocolStringListzurück. Wenn das Feld nicht gesetzt ist, wird eine leere Liste zurückgegeben.
Der Compiler generiert die folgenden Methoden nur im Builder der Nachricht
Builder setFoos(int index, String value): Setzt den Wert des Elements am gegebenen nullbasierten Index.Builder addFoos(String value): Fügt dem Feld ein neues Element mit dem gegebenen Wert hinzu.Builder addAllFoos(Iterable<? extends String> value): Fügt alle Elemente des gegebenenIterablezum Feld hinzu.Builder clearFoos(): Entfernt alle Elemente aus dem Feld. Nach dem Aufruf dieser Methode gibtgetFoosCount()null zurück.
Für andere einfache Feldtypen wird der entsprechende Java-Typ gemäß der Tabelle der Skalarwerttypen gewählt. Für Nachrichten- und Enum-Typen ist der Typ die Nachrichten- oder Enum-Klasse.
Wiederholte eingebettete Nachrichtenfelder
Für Nachrichtentypen akzeptieren setFoos() und addFoos() auch eine Instanz des Builder-Typs der Nachricht als Parameter. Dies ist nur eine Abkürzung, die dem Aufruf von .build() auf dem Builder und der Übergabe des Ergebnisses an die Methode entspricht. Es gibt auch eine zusätzliche generierte Methode
Builder addFoos(int index, Field value): Fügt ein neues Element am gegebenen nullbasierten Index ein. Verschiebt das Element, das sich derzeit an dieser Position befindet (falls vorhanden) und alle nachfolgenden Elemente nach rechts (erhöht ihre Indizes um eins).
Zusätzlich generiert der Compiler die folgenden zusätzlichen Zugriffsmethoden sowohl in der Nachrichtenklasse als auch in ihrem Builder für Nachrichtentypen, die es Ihnen ermöglichen, auf die relevanten Unter-Builder zuzugreifen
FooOrBuilder getFoosOrBuilder(int index): Gibt den Builder für das angegebene Element zurück, wenn er bereits existiert, oder wirftIndexOutOfBoundsException, wenn nicht. Wenn diese Methode von einer Nachrichtenklasse aufgerufen wird, gibt sie immer eine Nachricht (oder wirft eine Ausnahme) anstelle eines Builders zurück. Das Aufrufen dieser Methode auf Builder erstellt keinen Unter-Builder für das Feld.List<FooOrBuilder> getFoosOrBuilderList(): Gibt das gesamte Feld als unveränderliche Liste von Buildern (falls verfügbar) oder Nachrichten zurück, wenn nicht. Wenn diese Methode von einer Nachrichtenklasse aufgerufen wird, gibt sie immer eine unveränderliche Liste von Nachrichten anstelle einer unveränderlichen Liste von Buildern zurück.
Der Compiler generiert die folgenden Methoden nur im Builder der Nachricht
Builder getFoosBuilder(int index): Gibt den Builder für das Element am angegebenen Index zurück oder wirftIndexOutOfBoundsException, wenn der Index außerhalb der Grenzen liegt.Builder addFoosBuilder(int index): Fügt einen Builder für eine Standardnachrichteninstanz der wiederholten Nachricht am angegebenen Index ein und gibt ihn zurück. Die vorhandenen Einträge werden zu höheren Indizes verschoben, um Platz für den eingefügten Builder zu schaffen.Builder addFoosBuilder(): Fügt einen Builder für eine Standardnachrichteninstanz der wiederholten Nachricht hinzu und gibt ihn zurück.Builder removeFoos(int index): Entfernt das Element am gegebenen nullbasierten Index.List<Builder> getFoosBuilderList(): Gibt das gesamte Feld als unveränderliche Liste von Buildern zurück.
Wiederholte Enum-Felder (nur proto3)
Der Compiler generiert die folgenden zusätzlichen Methoden sowohl in der Nachrichtenklasse als auch in ihrem Builder
int getFoosValue(int index): Gibt den Ganzzahlwert des Enums am angegebenen Index zurück.List<java.lang.Integer> getFoosValueList(): Gibt das gesamte Feld als Liste von Integers zurück.
Der Compiler generiert die folgende zusätzliche Methode nur im Builder der Nachricht
Builder setFoosValue(int index, int value): Setzt den Ganzzahlwert des Enums am angegebenen Index.
Namenskollisionen
Wenn ein anderes nicht-wiederholtes Feld einen Namen hat, der mit einer der generierten Methoden des wiederholten Feldes kollidiert, wird beiden Feldnamen ihre Protobuf-Feldnummer am Ende angehängt.
Für diese Felddefinitionen
int32 foos_count = 1;
repeated string foos = 2;
Der Compiler benennt sie zunächst wie folgt um
int32 foos_count_1 = 1;
repeated string foos_2 = 2;
Die Zugriffsmethoden werden anschließend wie oben beschrieben generiert.
Oneof-Felder
Für diese oneof-Felddefinition
oneof choice {
int32 foo_int = 4;
string foo_string = 9;
...
}
Alle Felder im choice oneof verwenden ein einzelnes privates Feld für ihren Wert. Zusätzlich generiert der Protokollpuffer-Compiler einen Java-Enum-Typ für den oneof-Fall wie folgt
public enum ChoiceCase
implements com.google.protobuf.Internal.EnumLite {
FOO_INT(4),
FOO_STRING(9),
...
CHOICE_NOT_SET(0);
...
};
Die Werte dieses Enum-Typs haben die folgenden speziellen Methoden
int getNumber(): Gibt den numerischen Wert des Objekts zurück, wie in der .proto-Datei definiert.static ChoiceCase forNumber(int value): Gibt das Enum-Objekt zurück, das dem gegebenen numerischen Wert entspricht (odernullfür andere numerische Werte).
Der Compiler generiert die folgenden Zugriffsmethoden sowohl in der Nachrichtenklasse als auch in ihrem Builder
boolean hasFooInt(): Gibttruezurück, wenn der oneof-FallFOO_INTist.int getFooInt(): Gibt den aktuellen Wert vonfoozurück, wenn der oneof-FallFOO_INTist. Andernfalls gibt er den Standardwert dieses Feldes zurück.ChoiceCase getChoiceCase(): Gibt das Enum zurück, das angibt, welches Feld gesetzt ist. GibtCHOICE_NOT_SETzurück, wenn keines von ihnen gesetzt ist.
Der Compiler generiert die folgenden Methoden nur im Builder der Nachricht
Builder setFooInt(int value): SetztFooauf diesen Wert und setzt den oneof-Fall aufFOO_INT. Nach dem Aufruf dieser Methode gibthasFooInt()truezurück,getFooInt()gibtvaluezurück undgetChoiceCase()gibtFOO_INTzurück.Builder clearFooInt():- Nichts wird geändert, wenn der oneof-Fall nicht
FOO_INTist. - Wenn der oneof-Fall
FOO_INTist, wirdFooauf null gesetzt und der oneof-Fall aufCHOICE_NOT_SET. Nach dem Aufruf dieser Methode gibthasFooInt()falsezurück,getFooInt()gibt den Standardwert zurück undgetChoiceCase()gibtCHOICE_NOT_SETzurück.
- Nichts wird geändert, wenn der oneof-Fall nicht
Builder.clearChoice(): Setzt den Wert fürchoicezurück und gibt den Builder zurück.
Für andere einfache Feldtypen wird der entsprechende Java-Typ gemäß der Tabelle der Skalarwerttypen gewählt. Für Nachrichten- und Enum-Typen wird der Werttyp durch die Nachrichten- oder Enum-Klasse ersetzt.
Map-Felder
Für diese Map-Felddefinition
map<int32, int32> weight = 1;
Der Compiler generiert die folgenden Zugriffsmethoden sowohl in der Nachrichtenklasse als auch in ihrem Builder
Map<Integer, Integer> getWeightMap();: Gibt eine unveränderlicheMapzurück.int getWeightOrDefault(int key, int default);: Gibt den Wert für den Schlüssel oder den Standardwert zurück, wenn er nicht vorhanden ist.int getWeightOrThrow(int key);: Gibt den Wert für den Schlüssel zurück oder wirft IllegalArgumentException, wenn er nicht vorhanden ist.boolean containsWeight(int key);: Zeigt an, ob der Schlüssel in diesem Feld vorhanden ist.int getWeightCount();: Gibt die Anzahl der Elemente in der Map zurück.
Der Compiler generiert die folgenden Methoden nur im Builder der Nachricht
Builder putWeight(int key, int value);: Fügt das Gewicht zu diesem Feld hinzu.Builder putAllWeight(Map<Integer, Integer> value);: Fügt alle Einträge der gegebenen Map zu diesem Feld hinzu.Builder removeWeight(int key);: Entfernt das Gewicht aus diesem Feld.Builder clearWeight();: Entfernt alle Gewichte aus diesem Feld.@Deprecated Map<Integer, Integer> getMutableWeight();: Gibt eine veränderbareMapzurück. Beachten Sie, dass mehrere Aufrufe dieser Methode unterschiedliche Map-Instanzen zurückgeben können. Die zurückgegebene Map-Referenz kann durch nachfolgende Methodenaufrufe an den Builder ungültig gemacht werden.
Map-Felder mit Nachrichten als Wert
Für Maps mit Nachrichtentypen als Werten generiert der Compiler eine zusätzliche Methode im Builder der Nachricht
Foo.Builder putFooBuilderIfAbsent(int key);: Stellt sicher, dasskeyin der Zuordnung vorhanden ist, und fügt einen neuenFoo.Builderein, falls noch keiner existiert. Änderungen am zurückgegebenenFoo.Builderwerden in der endgültigen Nachricht widergespiegelt.
Any
Angenommen, ein Feld vom Typ Any wie folgt
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 Packen und Entpacken der Werte von Any
class Any {
// Packs the given message into an Any using the default type URL
// prefix “type.googleapis.com”.
public static Any pack(Message message);
// Packs the given message into an Any using the given type URL
// prefix.
public static Any pack(Message message,
String typeUrlPrefix);
// Checks whether this Any message’s payload is the given type.
public <T extends Message> boolean is(class<T> clazz);
// Checks whether this Any message’s payload has the same type as the given
// message.
public boolean isSameTypeAs(Message message);
// Unpacks Any into a message with the same type as the given messsage.
// Throws exception if the type doesn’t match or parsing the payload fails.
public <T extends Message> T unpackSameTypeAs(T message)
throws InvalidProtocolBufferException;
// Unpacks Any into the given message type. Throws exception if
// the type doesn’t match or parsing the payload has failed.
public <T extends Message> T unpack(class<T> clazz)
throws InvalidProtocolBufferException;
}
Aufzählungen (Enums)
Gegeben eine Enum-Definition wie
enum Foo {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
Der Protokollpuffer-Compiler generiert einen Java-Enum-Typ namens Foo mit demselben Satz von Werten. Wenn Sie proto3 verwenden, fügt er auch den speziellen Wert UNRECOGNIZED zum Enum-Typ hinzu. In Editions haben OPEN-Enums auch einen UNRECOGNIZED-Wert, während CLOSED-Enums dies nicht haben. Die Werte des generierten Enum-Typs haben die folgenden speziellen Methoden
int getNumber(): Gibt den numerischen Wert des Objekts zurück, wie in der.proto-Datei definiert.EnumValueDescriptor getValueDescriptor(): Gibt den Deskriptor des Wertes zurück, der Informationen über den Namen, die Nummer und den Typ des Wertes enthält.EnumDescriptor getDescriptorForType(): Gibt den Deskriptor des Enum-Typs zurück, der z. B. Informationen über jeden definierten Wert enthält.
Zusätzlich enthält der Enum-Typ Foo die folgenden statischen Methoden
static Foo forNumber(int value): Gibt das Enum-Objekt zurück, das dem gegebenen numerischen Wert entspricht. Gibt null zurück, wenn kein entsprechendes Enum-Objekt vorhanden ist.static Foo valueOf(int value): Gibt das Enum-Objekt zurück, das dem gegebenen numerischen Wert entspricht. Diese Methode ist zugunsten vonforNumber(int value)veraltet und wird in einer zukünftigen Version entfernt.static Foo valueOf(EnumValueDescriptor descriptor): Gibt das Enum-Objekt zurück, das dem gegebenen Wertdeskriptor entspricht. Kann schneller sein alsvalueOf(int). In proto3 undOPEN-Enums gibt esUNRECOGNIZEDzurück, wenn ein unbekannter Wertdeskriptor übergeben wird.EnumDescriptor getDescriptor(): Gibt den Deskriptor des Enum-Typs zurück, der z. B. Informationen über jeden definierten Wert enthält. (Dies unterscheidet sich vongetDescriptorForType()nur dadurch, dass es sich um eine statische Methode handelt.)
Eine Ganzzahlkonstante wird auch mit dem Suffix _VALUE für jeden Enum-Wert generiert.
Beachten Sie, dass die .proto-Sprache erlaubt, dass mehrere Enum-Symbole denselben numerischen Wert haben. Symbole mit demselben numerischen Wert sind Synonyme. Zum Beispiel
enum Foo {
BAR = 0;
BAZ = 0;
}
In diesem Fall ist BAZ ein Synonym für BAR. In Java wird BAZ als statisches final Feld wie folgt definiert
static final Foo BAZ = BAR;
Daher sind BAR und BAZ gleichwertig, und BAZ sollte niemals in switch-Anweisungen erscheinen. Der Compiler wählt immer das erste mit einem gegebenen numerischen Wert definierte Symbol als die "kanonische" Version dieses Symbols; alle nachfolgenden Symbole mit derselben Nummer sind nur Aliase.
Ein Enum kann verschachtelt innerhalb eines Nachrichtentyps definiert werden. Der Compiler generiert die Java-Enum-Definition verschachtelt innerhalb der Klasse dieses Nachrichtentyps.
Vorsicht: Bei der Generierung von Java-Code kann die maximale Anzahl von Werten in einem Protobuf-Enum überraschend niedrig sein – im schlimmsten Fall liegt das Maximum bei etwas über 1.700 Werten. Dieses Limit ist auf Größenbeschränkungen pro Methode für Java-Bytecode zurückzuführen und variiert je nach Java-Implementierung, verschiedenen Versionen der Protobuf-Suite und Optionen, die im .proto-File für das Enum gesetzt wurden.
Erweiterungen (Extensions)
Gegeben eine Nachricht mit einem Erweiterungsbereich
edition = "2023";
message Foo {
extensions 100 to 199;
}
Der Protokollpuffer-Compiler lässt Foo von GeneratedMessage.ExtendableMessage statt des üblichen GeneratedMessage erben. Ebenso wird der Builder von Foo von GeneratedMessage.ExtendableBuilder erben. Sie sollten niemals auf diese Basistypen namentlich verweisen (GeneratedMessage wird als Implementierungsdetail betrachtet). Diese Superklassen definieren jedoch eine Reihe zusätzlicher Methoden, mit denen Sie Erweiterungen manipulieren können.
Insbesondere Foo und Foo.Builder erben die Methoden hasExtension(), getExtension() und getExtensionCount(). Zusätzlich erbt Foo.Builder die Methoden setExtension() und clearExtension(). Jede dieser Methoden nimmt als ersten Parameter einen Erweiterungsidentifikator (wird unten beschrieben), der ein Erweiterungsfeld identifiziert. Die übrigen Parameter und der Rückgabewert sind genau dieselben wie bei den entsprechenden Zugriffsmethoden, die für ein normales (nicht-erweitertes) Feld desselben Typs wie der Erweiterungsidentifikator generiert würden.
Gegeben eine Erweiterungsdefinition
edition = "2023";
import "foo.proto";
extend Foo {
int32 bar = 123;
}
Der Protokollpuffer-Compiler generiert einen "Erweiterungsidentifikator" namens bar, den Sie mit den Erweiterungszugreifern von Foo verwenden können, um auf diese Erweiterung zuzugreifen, wie folgt
Foo foo =
Foo.newBuilder()
.setExtension(bar, 1)
.build();
assert foo.hasExtension(bar);
assert foo.getExtension(bar) == 1;
(Die genaue Implementierung von Erweiterungsidentifikatoren ist kompliziert und beinhaltet magischen Gebrauch von Generics – Sie müssen jedoch nicht wissen, wie Erweiterungsidentifikatoren funktionieren, um sie zu verwenden.)
Beachten Sie, dass bar als statisches Feld der Wrapper-Klasse für die .proto-Datei deklariert würde, wie oben beschrieben; wir haben den Namen der Wrapper-Klasse im Beispiel weggelassen.
Erweiterungen können innerhalb des Geltungsbereichs eines anderen Typs deklariert werden, um ihre generierten Symbolnamen zu präfixieren. Ein gängiges Muster ist beispielsweise, eine Nachricht durch ein Feld *innerhalb* der Deklaration des Typs des Feldes zu erweitern
edition = "2023";
import "foo.proto";
message Baz {
extend Foo {
Baz foo_ext = 124;
}
}
In diesem Fall wird eine Erweiterung mit dem Bezeichner foo_ext und dem Typ Baz innerhalb der Deklaration von Baz deklariert, und die Referenzierung von foo_ext erfordert die Hinzufügung eines Baz. Präfixes
Baz baz = createMyBaz();
Foo foo =
Foo.newBuilder()
.setExtension(Baz.fooExt, baz)
.build();
assert foo.hasExtension(Baz.fooExt);
assert foo.getExtension(Baz.fooExt) == baz;
Beim Parsen einer Nachricht, die Erweiterungen enthalten könnte, müssen Sie ein ExtensionRegistry bereitstellen, in dem Sie alle Erweiterungen registriert haben, die Sie parsen möchten. Andernfalls werden diese Erweiterungen wie unbekannte Felder behandelt und die Methoden, die Erweiterungen beobachten, verhalten sich so, als ob sie nicht existieren.
ExtensionRegistry registry = ExtensionRegistry.newInstance();
registry.add(Baz.fooExt);
Foo foo = Foo.parseFrom(input, registry);
assert foo.hasExtension(Baz.fooExt);
ExtensionRegistry registry = ExtensionRegistry.newInstance();
Foo foo = Foo.parseFrom(input, registry);
assert foo.hasExtension(Baz.fooExt) == false;
Services
Wenn die .proto-Datei die folgende Zeile enthält:
option java_generic_services = true;
Dann generiert der Protokollpuffer-Compiler Code basierend auf den in der Datei gefundenen Dienstdefinitionen, wie in diesem Abschnitt beschrieben. Der generierte Code ist jedoch möglicherweise unerwünscht, da er nicht an ein bestimmtes RPC-System gebunden ist und daher mehr Indirektionsstufen als Code erfordert, der auf ein System zugeschnitten ist. Wenn Sie nicht möchten, dass dieser Code generiert wird, fügen Sie diese Zeile zur Datei hinzu
option java_generic_services = false;
Wenn keine der oben genannten Zeilen angegeben ist, ist die Option standardmäßig false, da generische Dienste veraltet sind. (Beachten Sie, dass vor 2.4.0 die Option standardmäßig true ist)
RPC-Systeme, die auf .proto-Sprachservice-Definitionen basieren, sollten Plugins bereitstellen, um Code zu generieren, der für das System geeignet ist. Diese Plugins erfordern wahrscheinlich, dass abstrakte Dienste deaktiviert werden, damit sie eigene Klassen mit denselben Namen generieren können.
Der Rest dieses Abschnitts beschreibt, was der Protocol Buffer Compiler generiert, wenn abstrakte Dienste aktiviert sind.
Schnittstelle
Gegeben eine Service-Definition
service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}
Der Protokollpuffer-Compiler generiert eine abstrakte Klasse Foo, um diesen Dienst darzustellen. Foo hat eine abstrakte Methode für jede Methode, die in der Dienstdefinition enthalten ist. In diesem Fall ist die Methode Bar wie folgt definiert
abstract void bar(RpcController controller, FooRequest request,
RpcCallback<FooResponse> done);
Die Parameter sind äquivalent zu den Parametern von Service.CallMethod(), mit der Ausnahme, dass das Argument method impliziert ist und request und done ihren exakten Typ angeben.
Foo unterklassifiziert die Service-Schnittstelle. Der Protocol Buffer Compiler generiert automatisch Implementierungen der Methoden von Service wie folgt:
getDescriptorForType: Gibt denServiceDescriptordes Dienstes zurück.callMethod: Ermittelt anhand des bereitgestellten Methodendeskriptors, welche Methode aufgerufen wird, und ruft sie direkt auf, wobei die Request-Nachricht und der Callback auf die korrekten Typen abwärtskonvertiert werden.getRequestPrototypeundgetResponsePrototype: Gibt die Standardinstanz der Anfrage oder Antwort des korrekten Typs für die gegebene Methode zurück.
Die folgende statische Methode wird ebenfalls generiert
static ServiceDescriptor getServiceDescriptor(): Gibt den Deskriptor des Typs zurück, der Informationen darüber enthält, welche Methoden dieser Dienst hat und welche Eingabe- und Ausgabetypen sie haben.
Foo wird auch eine verschachtelte Schnittstelle Foo.Interface enthalten. Dies ist eine reine Schnittstelle, die wiederum Methoden enthält, die jeder Methode in Ihrer Dienstdefinition entsprechen. Diese Schnittstelle erbt jedoch nicht von der Service-Schnittstelle. Dies ist ein Problem, da RPC-Serverimplementierungen normalerweise für die Verwendung abstrakter Service-Objekte und nicht Ihres speziellen Dienstes geschrieben werden. Um dieses Problem zu lösen, können Sie, wenn Sie ein Objekt impl haben, das Foo.Interface implementiert, Foo.newReflectiveService(impl) aufrufen, um eine Instanz von Foo zu erstellen, die einfach an impl delegiert und Service implementiert.
Zusammenfassend lässt sich sagen, dass Sie bei der Implementierung Ihres eigenen Dienstes zwei Möglichkeiten haben
- Leiten Sie von
Fooab und implementieren Sie seine Methoden entsprechend, übergeben Sie dann Instanzen Ihrer abgeleiteten Klasse direkt an die RPC-Serverimplementierung. Dies ist in der Regel am einfachsten, wird aber von einigen als weniger „rein“ angesehen. - Implementieren Sie
Foo.Interfaceund verwenden SieFoo.newReflectiveService(Foo.Interface), um einenServicezu erstellen, der ihn umschließt, und übergeben Sie dann den Wrapper an Ihre RPC-Implementierung.
Stub
Der Protocol Buffer-Compiler generiert auch eine „Stub“-Implementierung jeder Dienstschnittstelle, die von Clients verwendet wird, die Anfragen an Server senden möchten, die den Dienst implementieren. Für den Foo-Dienst (oben) wird die Stub-Implementierung Foo.Stub als verschachtelte Klasse definiert.
Foo.Stub ist eine Unterklasse von Foo, die außerdem die folgenden Methoden implementiert
Foo.Stub(RpcChannel channel): Erstellt einen neuen Stub, der Anfragen über den gegebenen Kanal sendet.RpcChannel getChannel(): Gibt den Kanal dieses Stubs zurück, wie er an den Konstruktor übergeben wurde.
Der Stub implementiert zusätzlich jede der Dienstmethoden als Wrapper um den Kanal. Das Aufrufen einer der Methoden ruft einfach channel.callMethod() auf.
Die Protocol Buffer-Bibliothek enthält keine RPC-Implementierung. Sie enthält jedoch alle Werkzeuge, die Sie benötigen, um eine generierte Dienstklasse mit jeder beliebigen RPC-Implementierung Ihrer Wahl zu verbinden. Sie müssen lediglich Implementierungen von RpcChannel und RpcController bereitstellen.
Blockierende Schnittstellen
Die oben beschriebenen RPC-Klassen haben alle nicht-blockierende Semantik: Wenn Sie eine Methode aufrufen, stellen Sie ein Callback-Objekt bereit, das aufgerufen wird, sobald die Methode abgeschlossen ist. Oft ist es einfacher (wenn auch möglicherweise weniger skalierbar), Code mit blockierender Semantik zu schreiben, bei dem die Methode einfach nicht zurückkehrt, bis sie abgeschlossen ist. Um dem Rechnung zu tragen, generiert der Protocol Buffer-Compiler auch blockierende Versionen Ihrer Dienstklasse. Foo.BlockingInterface ist äquivalent zu Foo.Interface, mit der Ausnahme, dass jede Methode einfach das Ergebnis zurückgibt, anstatt einen Callback aufzurufen. So ist beispielsweise bar definiert als
abstract FooResponse bar(RpcController controller, FooRequest request)
throws ServiceException;
Analog zu nicht-blockierenden Diensten gibt Foo.newReflectiveBlockingService(Foo.BlockingInterface) einen BlockingService zurück, der eine Foo.BlockingInterface umschließt. Schließlich gibt Foo.BlockingStub eine Stub-Implementierung von Foo.BlockingInterface zurück, die Anfragen an einen bestimmten BlockingRpcChannel sendet.
Plugin-Einfügepunkte
Code-Generator-Plugins, die die Ausgabe des Java-Code-Generators erweitern möchten, können Code der folgenden Typen unter Angabe der entsprechenden Einfügepunktnamen einfügen.
outer_class_scope: Mitgliedsdeklarationen, die in die Wrapper-Klasse der Datei gehören.class_scope:TYPENAME: Mitgliedsdeklarationen, die in eine Nachrichtenklasse gehören.TYPENAMEist der vollständige Proto-Name, z. B.package.MessageType.builder_scope:TYPENAME: Mitgliedsdeklarationen, die in eine Builder-Klasse einer Nachricht gehören.TYPENAMEist der vollständige Proto-Name, z. B.package.MessageType.enum_scope:TYPENAME: Mitgliedsdeklarationen, die in eine Enum-Klasse gehören.TYPENAMEist der vollständige Proto-Enum-Name, z. B.package.EnumType.message_implements:TYPENAME: Klassenschnittstellendeklarationen für eine Nachrichtenklasse.TYPENAMEist der vollständige Proto-Name, z. B.package.MessageType.builder_implements:TYPENAME: Klassenschnittstellendeklarationen für eine Builder-Klasse.TYPENAMEist der vollständige Proto-Name, z. B.package.MessageType.
Generierter Code darf keine Import-Anweisungen enthalten, da diese zu Konflikten mit Typnamen führen können, die im generierten Code selbst definiert sind. Stattdessen müssen Sie beim Verweisen auf eine externe Klasse immer deren vollqualifizierten Namen verwenden.
Die Logik zur Bestimmung von Ausgabedateinamen im Java-Code-Generator ist ziemlich kompliziert. Sie sollten wahrscheinlich den protoc-Quellcode, insbesondere java_headers.cc, konsultieren, um sicherzustellen, dass Sie alle Fälle abgedeckt haben.
Generieren Sie keinen Code, der von privaten Klassenmitgliedern abhängt, die vom Standard-Code-Generator deklariert werden, da sich diese Implementierungsdetails in zukünftigen Versionen von Protocol Buffers ändern können.
Dienstprogramme
Protocol Buffer bietet Dienstprogramme für den Nachrichtenvergleich, die JSON-Konvertierung und die Arbeit mit Well-Known Types (vordefinierte Protocol Buffer-Nachrichten für häufige Anwendungsfälle).