- Appel du compilateur
- Colis
- Messages
- Fields
- Tout
- Une
- Énumérations
- Extensions
- Services
- Points d'insertion de plug-in
Cette page décrit précisément le code Java que le compilateur de tampon de protocole génère pour une définition de protocole donnée. Toute différence entre le code généré proto2 et le code généré proto3 est mise en évidence. Notez que ces différences résident dans le code généré tel que décrit dans ce document et non dans les classes/interfaces de messages de base, qui sont identiques dans les deux versions. Lisez le guide du langage proto2 et/ou le guide du langage proto3 avant de lire ce document.
Notez qu'aucune méthode de tampon de protocole Java n'accepte ni ne renvoie de valeur NULL, sauf indication contraire.
Appel du compilateur
Le compilateur de tampon de protocole génère une sortie Java lorsqu'elle est appelée avec l'option de ligne de commande --java_out=
. Le paramètre de l'option --java_out=
correspond au répertoire dans lequel vous souhaitez que le compilateur écrive votre sortie Java. Pour chaque entrée de fichier .proto
, le compilateur crée un fichier .java
de wrapper contenant une classe Java qui représente le fichier .proto
lui-même.
Si le fichier .proto
contient une ligne semblable à celle-ci :
option java_multiple_files = true;
Ensuite, le compilateur crée également un fichier .java
distinct pour chaque message de niveau supérieur, chaque énumération et chaque service déclarés dans le fichier .proto
.
Sinon, c'est-à-dire que lorsque l'option java_multiple_files
est définie sur "False", qui est la valeur par défaut, la classe wrapper mentionnée précédemment est également utilisée en tant que classe externe, et les classes/énumérations générées pour chaque message de niveau supérieur, énumération et service déclarés dans le fichier .proto
sont toutes imbriquées dans la classe de wrapper externe. Ainsi, le compilateur ne génère qu'un seul fichier .java
pour l'intégralité du fichier .proto
.
Le nom de la classe wrapper est choisi comme suit. Si le fichier .proto
contient une ligne semblable à celle-ci :
option java_outer_classname = "Foo";
Le nom de la classe du wrapper sera alors Foo
. Sinon, le nom de la classe du wrapper est déterminé en convertissant le nom de base du fichier .proto
en casse mixte. Par exemple, foo_bar.proto
va générer un nom de classe de FooBar
. S'il existe un service, une énumération ou un message (y compris des types imbriqués) dans le fichier portant le même nom, "OuterClass" sera ajouté au nom de la classe du wrapper. Exemples :
- Si
foo_bar.proto
contient un message appeléFooBar
, la classe wrapper générera un nom de classe deFooBarOuterClass
. - Si
foo_bar.proto
contient un service appeléFooService
et quejava_outer_classname
est également défini sur la chaîneFooService
, la classe wrapper générera un nom de classeFooServiceOuterClass
.
En plus des classes imbriquées, la classe wrapper présente elle-même l'API suivante (en supposant que la classe wrapper soit nommée Foo
et qu'elle ait été générée à partir de foo.proto
):
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) }
Le nom du package Java est choisi comme décrit dans la section Packages ci-dessous.
Le fichier de sortie est choisi en concaténant le paramètre en --java_out=
, le nom du package (avec les éléments .
remplacés par des éléments /
) et le nom du fichier .java
.
Par exemple, imaginons que vous invoquez le compilateur comme suit :
protoc --proto_path=src --java_out=build/gen src/foo.proto
Si le package Java de foo.proto
est com.example
et qu'il n'active pas java_multiple_files
et que son nom de classe externe est FooProtos
, le compilateur de tampon de protocole générera le fichier build/gen/com/example/FooProtos.java
. Le compilateur de tampon de protocole créera automatiquement les répertoires build/gen/com
et build/gen/com/example
, si nécessaire. Cependant, build/gen
ou build
ne seront pas créées, car elles doivent déjà exister. Vous pouvez spécifier plusieurs fichiers .proto
dans un même appel. Tous les fichiers de sortie seront générés en même temps.
Lorsque vous générez du code Java, la capacité du compilateur de tampon de protocole à générer directement des archives JAR est particulièrement pratique, car de nombreux outils Java peuvent lire le code source directement à partir de fichiers JAR. Pour générer un fichier JAR dans un fichier JAR, indiquez simplement un emplacement de sortie se terminant par .jar
. Notez que seul le code source Java est placé dans l'archive. Vous devez tout de même le compiler séparément pour produire des fichiers de classe Java.
Colis
La classe générée est placée dans un package Java en fonction de l'option java_package
. Si l'option est omise, la déclaration package
est utilisée à la place.
Par exemple, si le fichier .proto
contient :
package foo.bar;
La classe Java résultante sera ensuite placée dans le package Java foo.bar
. Toutefois, si le fichier .proto
contient également une option java_package
, comme suit :
package foo.bar; option java_package = "com.example.foo.bar";
Ensuite, la classe est placée dans le package com.example.foo.bar
. L'option java_package
est fournie, car les déclarations .proto
package
normales ne doivent pas commencer par un nom de domaine rétroactif.
Messages
Voici une déclaration de message simple :
message Foo {}
Le compilateur de tampon de protocole génère une classe appelée Foo
, qui implémente l'interface Message
. La classe est déclarée à l'état final
. Aucun autre sous-classement n'est autorisé. Foo
étend GeneratedMessage
, mais ceci doit être considéré comme un détail de l'implémentation. Par défaut, Foo
remplace de nombreuses méthodes de GeneratedMessage
par des versions spécialisées pour une vitesse maximale. Toutefois, si le fichier .proto
contient la ligne :
option optimize_for = CODE_SIZE;
alors Foo
remplacera uniquement l'ensemble minimal de méthodes nécessaires au fonctionnement et s'appuiera sur les implémentations basées sur la réflexion de GeneratedMessage
pour le reste. Cela réduit considérablement la taille du code généré, mais également les performances.
Si le répertoire de sortie Java utilisé par protoc est précédé du préfixe lite:
(par exemple, --java_out=lite:project/protos
), alors Foo
inclura les implémentations rapides de toutes les méthodes, mais implémentera l'interface MessageLite
, qui ne contient qu'un sous-ensemble des méthodes de Message
. En particulier, il n'est pas compatible avec les descripteurs ou la réflexion. Toutefois, dans ce mode, le code généré doit simplement être associé à libprotobuf-lite.jar
au lieu de libprotobuf.jar
.
La bibliothèque "allégée" est beaucoup plus petite que la bibliothèque complète. Elle est plus adaptée aux systèmes à ressources limitées, tels que les téléphones mobiles.
L'interface Message
définit des méthodes qui vous permettent de vérifier, de manipuler, de lire ou d'écrire l'intégralité du message. En plus de ces méthodes, la classe Foo
définit les méthodes statiques suivantes:
static Foo getDefaultInstance()
: renvoie l'instance singleton deFoo
. Le contenu de cette instance est identique à ce que vous obtiendriez si vous appeliezFoo.newBuilder().build()
(tous les champs au singulier sont non définis et tous les champs répétés sont vides). Notez que l'instance par défaut d'un message peut être utilisée comme une fabrique en appelant sa méthodenewBuilderForType()
.static Descriptor getDescriptor()
: renvoie le descripteur du type. Il contient des informations sur le type, y compris ses champs et leur type. Il peut être utilisé avec les méthodes de réflexion deMessage
, telles quegetField()
.static Foo parseFrom(...)
: analyse un message de typeFoo
provenant de la source donnée et le renvoie. Il existe une méthodeparseFrom
correspondant à chaque variante demergeFrom()
dans l'interfaceMessage.Builder
. Notez queparseFrom()
ne renvoie jamaisUninitializedMessageException
. Elle renvoieInvalidProtocolBufferException
si le message analysé ne contient pas de champs obligatoires. Par conséquent, il diffère subtilement de l'appel deFoo.newBuilder().mergeFrom(...).build()
.static Parser parser()
: renvoie une instance deParser
, qui implémente diverses méthodesparseFrom()
.Foo.Builder newBuilder()
: crée un compilateur (décrit ci-dessous).Foo.Builder newBuilder(Foo prototype)
: crée un compilateur avec tous les champs initialisés sur les mêmes valeurs que celles deprototype
. Les objets de message et de chaîne intégrés étant immuables, ils sont partagés entre l'original et la copie.
Chantiers
Les objets de message, tels que les instances de la classe Foo
décrite ci-dessus, sont immuables, tout comme un String
Java. Pour créer un objet message, vous devez utiliser un compilateur. Chaque classe de message possède sa propre classe de compilateur. Ainsi, dans notre exemple Foo
, le compilateur de tampon de protocole génère une classe imbriquée Foo.Builder
qui peut être utilisée pour créer un Foo
. Foo.Builder
implémente l'interface Message.Builder
. Elle étend la classe GeneratedMessage.Builder
, mais doit là encore être considérée comme un détail d'implémentation. Tout comme Foo
, Foo.Builder
peut s'appuyer sur des implémentations de méthodes génériques dans GeneratedMessage.Builder
ou, lorsque l'option optimize_for
est utilisée, générer du code personnalisé beaucoup plus rapide.
Foo.Builder
ne définit aucune méthode statique. Son interface est exactement telle que définie par l'interface Message.Builder
, à l'exception des types de renvoi qui sont plus spécifiques: les méthodes de Foo.Builder
qui modifient le type de renvoi du compilateur Foo.Builder
, et build()
renvoie le type Foo
.
Les méthodes qui modifient le contenu d'un compilateur, y compris les setters de champs, renvoient toujours une référence à cet outil. C'est-à-dire, "return this;
". Cela permet de regrouper plusieurs appels de méthode en une seule ligne. Exemple :
builder.mergeFrom(obj).setFoo(1).setBar("abc").clearBaz();
Notez que les outils de création ne sont pas sécurisés. Par conséquent, vous devez utiliser la synchronisation Java chaque fois que vous devez modifier le contenu d'un seul outil dans plusieurs threads différents.
Composants secondaires
Pour les messages contenant des sous-messages, le compilateur génère également des sous-compilateurs. Cela vous permet de modifier à plusieurs reprises des sous-messages imbriqués de façon répétée sans les recréer. Exemple :
message Foo { optional int32 val = 1; // some other fields. } message Bar { optional Foo foo = 1; // some other fields. } message Baz { optional Bar bar = 1; // some other fields. }
Si vous avez déjà un message Baz
et que vous souhaitez modifier l'élément val
profondément imbriqué dans Foo
. Au lieu de :
baz = baz.toBuilder().setBar( baz.getBar().toBuilder().setFoo( baz.getBar().getFoo().toBuilder().setVal(10).build() ).build()).build();
Vous pouvez écrire :
Baz.Builder builder = baz.toBuilder(); builder.getBarBuilder().getFooBuilder().setVal(10); baz = builder.build();
Types imbriqués
Un message peut être déclaré dans un autre message. Exemple :
message Foo {
message Bar {
}
}
Dans ce cas, le compilateur génère simplement Bar
en tant que classe interne imbriquée dans Foo
.
Champs
Outre les méthodes décrites dans la section précédente, le compilateur de tampon de protocole génère un ensemble de méthodes d'accès pour chaque champ défini dans le message du fichier .proto
. Les méthodes qui lisent la valeur du champ sont définies à la fois dans la classe de message et dans le compilateur correspondant. Les méthodes qui modifient la valeur sont définies uniquement dans le compilateur.
Notez que les noms de méthodes utilisent toujours une dénomination en camel case, même si le nom du champ du fichier .proto
utilise des minuscules et des traits de soulignement (comme il se doit). La conversion de la casse se déroule comme suit:
- Chaque trait de soulignement dans le nom est supprimé, et la lettre suivante est en majuscule.
- Si le nom comporte un préfixe tel que "get", la première lettre est en majuscule. Sinon, il est en minuscules.
Ainsi, le champ foo_bar_baz
devient fooBarBaz
. S'il avait le préfixe get
, il s'agirait de getFooBarBaz
.
Dans certains cas particuliers, lorsqu'un nom de méthode est en conflit avec des mots réservés en Java ou avec des méthodes déjà définies dans la bibliothèque protobuf, un trait de soulignement est ajouté.
Par exemple, le getter pour un champ nommé class
est getClass_
pour éviter tout conflit avec la méthode getClass
dans java.lang.Object
.
En plus des méthodes d'accesseur, le compilateur génère une constante entière pour chaque champ contenant son numéro de champ. Le nom constant correspond au nom du champ converti en majuscules suivi de _FIELD_NUMBER
. Par exemple, pour le champ optional int32 foo_bar = 5;
, le compilateur générera la constante public static final int FOO_BAR_FIELD_NUMBER = 5;
.
Champs singuliers (proto2)
Pour l'une des définitions de champs suivantes :
optional int32 foo = 1; required int32 foo = 1;
Le compilateur va générer les méthodes d'accesseur suivantes dans la classe de message et dans son compilateur:
boolean hasFoo()
: renvoietrue
si le champ est défini.int getFoo()
: affiche la valeur actuelle du champ. Si le champ n'est pas défini, affiche la valeur par défaut.
Le compilateur ne générera les méthodes suivantes que dans l'outil de création du message:
Builder setFoo(int value)
: définit la valeur du champ. Après avoir appelé cela,hasFoo()
renvoietrue
etgetFoo()
renvoievalue
.Builder clearFoo()
: efface la valeur du champ. Après avoir appelé cela,hasFoo()
renvoiefalse
etgetFoo()
renvoie la valeur par défaut.
Pour les autres types de champs simples, le type Java correspondant est choisi conformément à la table des types de valeurs scalaires. Pour les types de messages et d'énumération, le type de valeur est remplacé par la classe de message ou d'énumération.
Champs de message intégrés
Pour les types de messages, setFoo()
accepte également une instance du type de compilateur du message en tant que paramètre. Il s'agit simplement d'un raccourci qui équivaut à appeler .build()
sur le compilateur et à transmettre le résultat à la méthode.
Si le champ n'est pas défini, getFoo()
affiche une instance Foo dont aucun des champs n'est défini (il peut s'agir de l'instance renvoyée par Foo.getDefaultInstance()
).
En outre, le compilateur génère deux méthodes d'accesseur qui vous permettent d'accéder aux sous-compilateurs pertinents pour les types de messages. La méthode suivante est générée à la fois dans la classe de message et dans son compilateur:
FooOrBuilder getFooOrBuilder()
: affiche l'outil de création du champ, s'il existe déjà, ou le message, s'il n'existe pas. L'appel de cette méthode sur les compilateurs ne crée pas de sous-compilateur pour le champ.
Le compilateur ne génère la méthode suivante que dans l'outil de création du message.
Builder getFooBuilder()
: renvoie le compilateur pour le champ.
Champs singuliers (proto3)
Pour cette définition de champ :
int32 foo = 1;
Le compilateur génère la méthode d'accesseur suivante dans la classe de message et dans son compilateur:
int getFoo()
: affiche la valeur actuelle du champ. Si le champ n'est pas défini, affiche la valeur par défaut correspondant au type du champ.
Le compilateur ne générera les méthodes suivantes que dans l'outil de création du message:
Builder setFoo(int value)
: définit la valeur du champ. Après avoir appelé cela,getFoo()
renvoievalue
.Builder clearFoo()
: efface la valeur du champ. Une fois la méthode appelée,getFoo()
affiche la valeur par défaut correspondant au type du champ.
Pour les autres types de champs simples, le type Java correspondant est choisi conformément à la table des types de valeurs scalaires. Pour les types de messages et d'énumération, le type de valeur est remplacé par la classe de message ou d'énumération.
Champs de message intégrés
Pour les types de champs de message, une méthode d'accesseur supplémentaire est générée à la fois dans la classe de message et dans son compilateur:
boolean hasFoo()
: renvoietrue
si le champ a été défini.
setFoo()
accepte également une instance du type de compilateur du message comme paramètre. Il s'agit simplement d'un raccourci qui équivaut à appeler .build()
sur le compilateur et à transmettre le résultat à la méthode.
Si le champ n'est pas défini, getFoo()
affiche une instance Foo dont aucun des champs n'est défini (il peut s'agir de l'instance renvoyée par Foo.getDefaultInstance()
).
En outre, le compilateur génère deux méthodes d'accesseur qui vous permettent d'accéder aux sous-compilateurs pertinents pour les types de messages. La méthode suivante est générée à la fois dans la classe de message et dans son compilateur:
FooOrBuilder getFooOrBuilder()
: affiche l'outil de création du champ, s'il existe déjà, ou le message, s'il n'existe pas. L'appel de cette méthode sur les compilateurs ne crée pas de sous-compilateur pour le champ.
Le compilateur ne génère la méthode suivante que dans l'outil de création du message.
Builder getFooBuilder()
: renvoie le compilateur pour le champ.
Champs d'énumération
Pour les types de champs enum, une méthode d'accesseur supplémentaire est générée à la fois dans la classe de message et dans son compilateur:
int getFooValue()
: renvoie la valeur entière de l'énumération.
Le compilateur ne générera la méthode supplémentaire suivante que dans le compilateur du message:
Builder setFooValue(int value)
: définit la valeur entière de l'énumération.
En outre, getFoo()
renvoie UNRECOGNIZED
si la valeur d'énumération est inconnue. Il s'agit d'une valeur supplémentaire spéciale ajoutée par le compilateur proto3 au type enum généré.
Champs répétés
Pour cette définition de champ :
repeated string foo = 1;
Le compilateur va générer les méthodes d'accesseur suivantes dans la classe de message et dans son compilateur:
int getFooCount()
: renvoie le nombre d'éléments actuellement dans le champ.String getFoo(int index)
: renvoie l'élément avec l'index basé sur zéro donné.ProtocolStringList getFooList()
: renvoie l'intégralité du champ en tant queProtocolStringList
. Si le champ n'est pas défini, renvoie une liste vide.
Le compilateur ne générera les méthodes suivantes que dans l'outil de création du message:
Builder setFoo(int index, String value)
: définit la valeur de l'élément à l'index basé sur zéro donné.Builder addFoo(String value)
: ajoute un nouvel élément au champ avec la valeur donnée.Builder addAllFoo(Iterable<? extends String> value)
: ajoute tous les éléments du champIterable
donné au champ.Builder clearFoo()
: supprime tous les éléments du champ. Après avoir appelé cela,getFooCount()
renvoie zéro.
Pour les autres types de champs simples, le type Java correspondant est choisi conformément à la table des types de valeurs scalaires. Pour les types de message et d'énumération, le type est le message ou la classe d'énumération.
Champs de messages intégrés répétés
Pour les types de messages, setFoo()
et addFoo()
acceptent également une instance du type de compilateur du message comme paramètre. Il s'agit simplement d'un raccourci qui équivaut à appeler .build()
sur le compilateur et à transmettre le résultat à la méthode. Une autre méthode est également générée:
Builder addFoo(int index, Field value)
: insère un nouvel élément à l'index basé sur zéro donné. Décale l'élément qui se trouve actuellement à cette position (le cas échéant) et les éléments suivants vers la droite (en ajoute un à leur index).
De plus, le compilateur génère les méthodes d'accesseur supplémentaires suivantes dans la classe de message et dans son compilateur pour les types de messages, ce qui vous permet d'accéder aux sous-compilateurs pertinents:
FooOrBuilder getFooOrBuilder(int index)
: renvoie le compilateur pour l'élément spécifié, s'il existe déjà, ou si l'élément n'existe pas. Si cette méthode est appelée à partir d'une classe de message, elle renvoie toujours un message plutôt qu'un compilateur. L'appel de cette méthode sur les compilateurs ne crée pas de sous-compilateur pour le champ.List<FooOrBuilder> getFooOrBuilderList()
: renvoie le champ entier sous la forme d'une liste non modifiable de compilateurs (le cas échéant) ou de messages dans le cas contraire. Si cette méthode est appelée à partir d'une classe de message, elle renvoie toujours une liste immuable de messages plutôt qu'une liste non modifiable des compilateurs.
Le compilateur ne générera les méthodes suivantes que dans l'outil de création du message:
Builder getFooBuilder(int index)
: renvoie le compilateur pour l'élément avec l'index spécifié.Builder addFooBuilder(int index)
: insère et renvoie un compilateur pour une instance de message par défaut à l'index spécifié. Les entrées existantes sont déplacées vers des index plus élevés pour libérer de l'espace pour le compilateur inséré.Builder addFooBuilder()
: ajoute et affiche un compilateur pour une instance de message par défaut.Builder removeFoo(int index)
: supprime l'élément ayant l'index basé sur zéro donné.List<Builder> getFooBuilderList()
: renvoie le champ entier sous la forme d'une liste non modifiable de compilateurs.
Champs d'énumération répétés (proto3 uniquement)
Le compilateur va générer les méthodes supplémentaires suivantes dans la classe de message et dans son compilateur:
int getFooValue(int index)
: renvoie la valeur entière de l'énumération avec l'index spécifié.List<java.lang.Integer> getFooValueList()
: renvoie l'intégralité du champ sous la forme d'une liste d'entiers.
Le compilateur ne générera la méthode supplémentaire suivante que dans le compilateur du message:
Builder setFooValue(int index, int value)
: définit la valeur entière de l'énumération sur l'index spécifié.
Conflits de noms
Si le nom d'un autre champ non répété est en conflit avec l'une des méthodes générées du champ répété, le numéro de champ protobuf est ajouté à la fin du nom de chaque champ.
Pour ces définitions de champs :
int32 foo_count = 1; repeated string foo = 2;
Le compilateur les renommera d'abord comme suit :
int32 foo_count_1 = 1; repeated string foo_2 = 2;Les méthodes accesseur seront ensuite générées comme décrit ci-dessus.
Un champ
Pour cette définition de champ :
oneof example_name { int32 foo = 1; ... }
Le compilateur va générer les méthodes d'accesseur suivantes dans la classe de message et dans son compilateur:
boolean hasFoo()
(proto2 uniquement): renvoietrue
si l'un des cas estFOO
.int getFoo()
: renvoie la valeur actuelle deexample_name
si l'un des cas estFOO
. Sinon, renvoie la valeur par défaut de ce champ.
Le compilateur ne générera les méthodes suivantes que dans l'outil de création du message:
Builder setFoo(int value)
: définitexample_name
sur cette valeur et définit l'un des cas surFOO
. Après avoir appelé cela,hasFoo()
renvoietrue
,getFoo()
renvoievalue
etgetExampleNameCase()
renvoieFOO
.Builder clearFoo()
:- Rien ne changera si la casse n'est pas
FOO
. - Si l'un des cas est
FOO
, définitexample_name
sur "null" et l'un surEXAMPLENAME_NOT_SET
. Après avoir appelé cela,hasFoo()
renvoiefalse
,getFoo()
renvoie la valeur par défaut etgetExampleNameCase()
renvoieEXAMPLENAME_NOT_SET
.
- Rien ne changera si la casse n'est pas
Pour les autres types de champs simples, le type Java correspondant est choisi conformément au tableau des types de valeurs scalaires. Pour les types de messages et d'énumération, le type de valeur est remplacé par la classe de message ou d'énumération.
Champs de carte
Pour cette définition de champ de mappage :
map<int32, int32> weight = 1;
Le compilateur va générer les méthodes d'accesseur suivantes dans la classe de message et dans son compilateur:
Map<Integer, Integer> getWeightMap();
: renvoie unMap
non modifiable.int getWeightOrDefault(int key, int default);
: affiche la valeur de clé ou la valeur par défaut si celle-ci n'est pas présente.int getWeightOrThrow(int key);
: renvoie la valeur de la clé ou renvoie IllegalArgumentException s'il n'est pas présent.boolean containsWeight(int key);
: indique si la clé est présente dans ce champ.int getWeightCount();
: renvoie le nombre d'éléments dans la carte.
Le compilateur ne générera les méthodes suivantes que dans l'outil de création du message:
Builder putWeight(int key, int value);
: ajoutez la pondération à ce champ.Builder putAllWeight(Map<Integer, Integer> value);
: ajoute à ce champ toutes les entrées de la carte donnée.Builder removeWeight(int key);
: supprime la pondération de ce champ.Builder clearWeight();
: supprime toutes les pondérations de ce champ.@Deprecated Map<Integer, Integer> getMutableWeight();
: renvoie uneMap
modifiable. Notez que plusieurs appels à cette méthode peuvent renvoyer des instances de carte différentes. La référence de carte renvoyée peut être invalidée par tous les appels de méthode ultérieurs au compilateur.
Tout
Exemple de champ Any
semblable à celui-ci :
import "google/protobuf/any.proto"; message ErrorStatus { string message = 1; google.protobuf.Any details = 2; }
Dans notre code généré, le getter du champ details
renvoie une instance de com.google.protobuf.Any
. Cette méthode offre les méthodes spéciales suivantes pour empaqueter et décompresser les valeurs 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); // 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; }
Un
Selon l'une des définitions suivantes :
oneof example_name { int32 foo_int = 4; string foo_string = 9; ... }
Tous les champs du champ example_name
utiliseront un seul champ privé pour la valeur. De plus, le compilateur de tampon de protocole générera un type d'énumération Java pour l'un des cas, comme suit :
public enum ExampleNameCase implements com.google.protobuf.Internal.EnumLite { FOO_INT(4), FOO_STRING(9), ... EXAMPLENAME_NOT_SET(0); ... };
Les valeurs de ce type d'énumération sont soumises aux méthodes spéciales suivantes:
int getNumber()
: renvoie la valeur numérique de l'objet telle que définie dans le fichier .proto.static ExampleNameCase forNumber(int value)
: renvoie l'objet enum correspondant à la valeur numérique donnée (ounull
pour les autres valeurs numériques).
Le compilateur va également générer la méthode d'accesseur suivante dans la classe de message et dans son compilateur:
ExampleNameCase getExampleNameCase()
: renvoie l'énumération qui indique le champ défini. RenvoieEXAMPLENAME_NOT_SET
si aucune d'entre elles n'est définie.
Le compilateur ne générera la méthode suivante que dans l'outil de création du message:
Builder clearExampleName()
: réinitialise le champ privé de l'un des champs sur "null", puis définit celui-ci surEXAMPLENAME_NOT_SET
.
Énumérations
Voici une définition d'énumération telle que:
enum Foo { A = 0; B = 5; C = 1234; }
Le compilateur de tampon de protocole va générer une énumération Java appelée Foo
avec le même ensemble de valeurs. Si vous utilisez proto3, il ajoute également la valeur spéciale UNRECOGNIZED
au type d'énumération. Les valeurs du type d'énumération généré ont les méthodes spéciales suivantes:
int getNumber()
: renvoie la valeur numérique de l'objet telle que définie dans le fichier.proto
.EnumValueDescriptor getValueDescriptor()
: renvoie le descripteur de la valeur, qui contient des informations sur le nom, le nombre et le type de la valeur.EnumDescriptor getDescriptorForType()
: renvoie le descripteur du type d'énumération, qui contient des informations sur chaque valeur définie.
En outre, le type d'énumération Foo
contient les méthodes statiques suivantes:
static Foo forNumber(int value)
: renvoie l'objet enum correspondant à la valeur numérique donnée. Renvoie la valeur "null" lorsqu'il n'y a pas d'objet enum correspondant.static Foo valueOf(int value)
: renvoie l'objet enum correspondant à la valeur numérique donnée. Cette méthode a été abandonnée au profit deforNumber(int value)
et sera supprimée dans une prochaine version.static Foo valueOf(EnumValueDescriptor descriptor)
: affiche l'objet enum correspondant au descripteur de valeur donné. Peut être plus rapide quevalueOf(int)
. Dans proto3, la fonction renvoieUNRECOGNIZED
si elle transmet un descripteur de valeur inconnu.EnumDescriptor getDescriptor()
: renvoie le descripteur du type d'énumération, qui contient des informations sur chaque valeur définie. (Contrairement à la méthodegetDescriptorForType()
, cette méthode est statique.)
Une constante entière est également générée avec le suffixe _VALUE pour chaque valeur d'énumération.
Notez que la langue .proto
permet à plusieurs symboles d'énumération d'avoir la même valeur numérique. Les symboles ayant la même valeur numérique sont des synonymes. Exemple :
enum Foo { BAR = 0; BAZ = 0; }
Dans ce cas, BAZ
est un synonyme de BAR
. En Java, BAZ
sera défini comme un champ final statique comme suit :
static final Foo BAZ = BAR;
Ainsi, BAR
et BAZ
sont égaux, et BAZ
ne doit jamais apparaître dans les instructions switch. Le compilateur choisit toujours le premier symbole défini avec une valeur numérique comme version "canonique" de ce symbole. Tous les symboles ultérieurs comportant le même nombre ne sont que des alias.
Une énumération peut être imbriquée dans un type de message. Le compilateur génère la définition d'énumération Java imbriquée dans la classe de ce type de message.
Extensions (proto2 uniquement)
Message reçu avec une plage d'extensions :
message Foo { extensions 100 to 199; }
Le compilateur de tampon de protocole demandera à Foo
d'étendre GeneratedMessage.ExtendableMessage
au lieu de GeneratedMessage
habituel. De même, l'outil de création de Foo
étend GeneratedMessage.ExtendableBuilder
. Vous ne devez jamais faire référence à ces types de base par leur nom (GeneratedMessage
est considéré comme un détail de l'implémentation). Toutefois, ces superclasses définissent un certain nombre de méthodes supplémentaires que vous pouvez utiliser pour manipuler les extensions.
Plus particulièrement, Foo
et Foo.Builder
hériteront des méthodes hasExtension()
, getExtension()
et getExtensionCount()
. De plus, Foo.Builder
héritera des méthodes setExtension()
et clearExtension()
. Chacune de ces méthodes utilise comme premier paramètre un identifiant d'extension (décrit ci-dessous), qui identifie un champ d'extension. Les paramètres restants et la valeur renvoyée sont exactement les mêmes que ceux des méthodes d'accesseur correspondantes qui seraient générées pour un champ normal (sans extension) du même type que l'identifiant de l'extension.
Selon une définition d'extension :
extend Foo { optional int32 bar = 123; }
Le compilateur de tampon de protocole génère un "identifiant d'extension" appelé bar
, que vous pouvez utiliser avec les accesseurs d'extension de Foo
pour accéder à cette extension, comme suit :
Foo foo = Foo.newBuilder() .setExtension(bar, 1) .build(); assert foo.hasExtension(bar); assert foo.getExtension(bar) == 1;
L'implémentation exacte des identifiants d'extension est complexe et implique l'utilisation magique de génériques. Toutefois, vous n'avez pas à vous soucier du fonctionnement des identifiants d'extension.
Notez que bar
serait déclaré en tant que champ statique de la classe wrapper pour le fichier .proto
, comme décrit ci-dessus. Nous avons omis le nom de la classe wrapper dans l'exemple.
Les extensions peuvent être déclarées dans le champ d'application d'un autre type pour préfixer les noms de symboles générés. Par exemple, un modèle courant consiste à étendre un message d'un champ dans la déclaration du type du champ :
message Baz { extend Foo { optional Baz foo_ext = 124; } }
Dans ce cas, une extension avec l'identifiant foo_ext
et le type Baz
est déclarée dans la déclaration de Baz
, et l'extension foo_ext
nécessite l'ajout du préfixe Baz.
:
Baz baz = createMyBaz(); Foo foo = Foo.newBuilder() .setExtension(Baz.fooExt, baz) .build(); assert foo.hasExtension(Baz.fooExt); assert foo.getExtension(Baz.fooExt) == baz;
Lorsque vous analysez un message pouvant inclure des extensions, vous devez fournir un registre ExtensionRegistry dans lequel vous avez enregistré toutes les extensions que vous souhaitez pouvoir analyser. Sinon, ces extensions seront traitées comme des champs inconnus et les méthodes observant les extensions se comporteront comme si elles n'existent pas.
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
Si le fichier .proto
contient la ligne suivante:
option java_generic_services = true;
Le compilateur de tampon de protocole générera ensuite du code en fonction des définitions de service trouvées dans le fichier, comme décrit dans cette section. Toutefois, le code généré peut ne pas être souhaitable, car il n'est lié à aucun système RPC particulier et nécessite donc plus de niveaux d'indirection que le code conçu pour un seul système. Si vous NE souhaitez PAS que ce code soit généré, ajoutez la ligne suivante au fichier:
option java_generic_services = false;
Si aucune de ces lignes n'est indiquée, l'option par défaut est false
, car les services génériques sont obsolètes.
Notez qu'avant la version 2.4.0, l'option est définie par défaut sur true
.
Les systèmes RPC basés sur les définitions de service en .proto
doivent fournir des plug-ins permettant de générer du code approprié au système. Ces plug-ins sont susceptibles d'exiger que les services abstraits soient désactivés pour pouvoir générer leurs propres classes du même nom. Les plug-ins sont nouveaux dans la version 2.3.0 (janvier 2010).
Le reste de cette section décrit ce que le compilateur de tampon de protocole génère lorsque les services abstraits sont activés.
Interface
Selon une définition de service :
service Foo { rpc Bar(FooRequest) returns(FooResponse); }
Le compilateur de tampon de protocole générera une classe abstraite Foo
pour représenter ce service. Foo
comporte une méthode abstraite pour chaque méthode définie dans la définition de service. Dans ce cas, la méthode Bar
est définie comme suit :
abstract void bar(RpcController controller, FooRequest request, RpcCallback<FooResponse> done);
Les paramètres sont équivalents aux paramètres de Service.CallMethod()
, sauf que l'argument method
est implicite et que request
et done
spécifient leur type exact.
Foo
sous-classe l'interface Service
. Le compilateur de tampon de protocole génère automatiquement des implémentations des méthodes de Service
comme suit:
getDescriptorForType
: renvoie la valeurServiceDescriptor
du service.callMethod
: détermine quelle méthode est appelée en fonction du descripteur de méthode fourni et l'appelle directement, en abaissant le message de requête et le rappel aux bons types.getRequestPrototype
etgetResponsePrototype
: affiche l'instance par défaut de la requête ou de la réponse du type approprié pour la méthode donnée.
La méthode statique suivante est également générée:
static ServiceDescriptor getServiceDescriptor()
: renvoie le descripteur du type, qui contient des informations sur les méthodes de ce service et sur leurs types d'entrée et de sortie.
Foo
contiendra également une interface imbriquée Foo.Interface
. Il s'agit d'une interface pure contenant à nouveau des méthodes correspondant à chaque méthode de votre définition de service. Cependant, cette interface n'étend pas l'interface Service
. Cela pose problème, car les implémentations de serveur RPC sont généralement écrites pour utiliser des objets Service
abstraits, et non votre service particulier. Pour résoudre ce problème, si un objet impl
implémente Foo.Interface
, vous pouvez appeler Foo.newReflectiveService(impl)
pour construire une instance de Foo
qui délègue simplement à impl
et implémente Service
.
Pour résumer, lorsque vous implémentez votre propre service, deux options s'offrent à vous:
- Sous-classe
Foo
et implémentez ses méthodes le cas échéant, puis transférez les instances de votre sous-classe directement à l'implémentation du serveur RPC. C'est généralement plus facile, mais certains le considèrent moins "pur". - Implémentez
Foo.Interface
et utilisezFoo.newReflectiveService(Foo.Interface)
pour construire unService
qui l'enveloppe, puis transmettez le wrapper à votre implémentation RPC.
Stub
Le compilateur de tampon de protocole génère également une implémentation « bouchon » de chaque interface de service, qui est utilisée par les clients souhaitant envoyer des requêtes aux serveurs mettant en œuvre le service. Pour le service Foo
(ci-dessus), l'implémentation de bouchon Foo.Stub
sera définie en tant que classe imbriquée.
Foo.Stub
est une sous-classe de Foo
qui implémente également les méthodes suivantes:
Foo.Stub(RpcChannel channel)
: crée un bouchon qui envoie des requêtes sur le canal donné.RpcChannel getChannel()
: renvoie le canal de cette simulation, tel qu'il est transmis au constructeur.
Le bouchon met en œuvre chacune des méthodes du service en tant que wrapper autour du canal. L'appel de l'une des méthodes appelle simplement channel.callMethod()
.
La bibliothèque de tampons de protocole n'inclut pas de mise en œuvre RPC. Toutefois, elle inclut tous les outils dont vous avez besoin pour relier une classe de service générée à l'implémentation RPC arbitraire de votre choix. Il vous suffit de fournir des implémentations de RpcChannel
et RpcController
.
Interfaces de blocage
Les classes RPC décrites ci-dessus présentent toutes une sémantique non bloquante: lorsque vous appelez une méthode, vous fournissez un objet de rappel qui sera invoqué une fois la méthode terminée. Il est souvent plus facile (mais peut-être moins évolutif) d'écrire du code à l'aide d'une sémantique de blocage, où la méthode ne renvoie simplement qu'une fois l'opération terminée. Pour cela, le compilateur de tampon de protocole génère également des versions bloquantes de votre classe de service. Foo.BlockingInterface
équivaut à Foo.Interface
, mais chaque méthode renvoie simplement le résultat au lieu d'appeler un rappel. Ainsi, par exemple, bar
est défini comme suit :
abstract FooResponse bar(RpcController controller, FooRequest request) throws ServiceException;
Comme pour les services non bloquants, Foo.newReflectiveBlockingService(Foo.BlockingInterface)
renvoie un BlockingService
encapsulant du Foo.BlockingInterface
. Enfin, Foo.BlockingStub
renvoie une implémentation de simulation de Foo.BlockingInterface
qui envoie des requêtes à un BlockingRpcChannel
particulier.
Points d'insertion des plug-ins
Les plug-ins de génération de code qui souhaitent étendre la sortie du générateur de code Java peuvent insérer du code des types suivants en utilisant les noms de point d'insertion donnés.
outer_class_scope
: déclarations de membre qui appartiennent à la classe wrapper du fichier.class_scope:TYPENAME
: déclarations de membre qui appartiennent à une classe de message.TYPENAME
est le nom proto complet, tel quepackage.MessageType
.builder_scope:TYPENAME
: déclarations de membre qui appartiennent à la classe de compilateur d'un message.TYPENAME
est le nom proto complet, tel quepackage.MessageType
.enum_scope:TYPENAME
: déclarations de membre qui appartiennent à une classe d'énumération.TYPENAME
est le nom complet de l'énumération proto, tel quepackage.EnumType
.message_implements:TYPENAME
: déclarations d'implémentation de la classe pour un message.TYPENAME
est le nom proto complet, tel quepackage.MessageType
.builder_implements:TYPENAME
: déclarations d'implémentation de classe pour une classe de compilateur.TYPENAME
est le nom proto complet, tel quepackage.MessageType
.
Le code généré ne peut pas contenir d'instructions d'importation, car elles sont sujettes aux conflits de noms de types définis dans le code généré. Lorsque vous faites référence à une classe externe, vous devez toujours utiliser son nom complet.
La logique pour déterminer les noms des fichiers de sortie dans le générateur de code Java est assez compliquée. Nous vous conseillons d'examiner le code source de protoc
, en particulier java_headers.cc
, pour vous assurer que vous avez bien traité tous les cas.
Ne générez pas de code reposant sur des membres de classe privée déclarés par le générateur de code standard, car ces détails d'implémentation peuvent changer dans les futures versions de Protocol Buffers.
Classes utilitaires
Le tampon de protocole fournit des classes d'utilitaires pour la comparaison de messages, la conversion JSON et l'utilisation de types connus (messages de tampon de protocole prédéfinis pour les cas d'utilisation courants).