REMARQUE:Ce site est obsolète. Le site sera désactivé après le 31 janvier 2023 et le trafic redirigera vers le nouveau site à l'adresse https://protobuf.dev. En attendant, les mises à jour ne seront effectuées que sur protobuf.dev.

Code généré par Java

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

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 de FooBarOuterClass.
  • Si foo_bar.proto contient un service appelé FooService et que java_outer_classname est également défini sur la chaîne FooService, la classe wrapper générera un nom de classe FooServiceOuterClass.

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 de Foo. Le contenu de cette instance est identique à ce que vous obtiendriez si vous appeliez Foo.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éthode newBuilderForType().
  • 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 de Message, telles que getField().
  • static Foo parseFrom(...) : analyse un message de type Foo provenant de la source donnée et le renvoie. Il existe une méthode parseFrom correspondant à chaque variante de mergeFrom() dans l'interface Message.Builder. Notez que parseFrom() ne renvoie jamais UninitializedMessageException. Elle renvoie InvalidProtocolBufferException si le message analysé ne contient pas de champs obligatoires. Par conséquent, il diffère subtilement de l'appel de Foo.newBuilder().mergeFrom(...).build().
  • static Parser parser(): renvoie une instance de Parser, qui implémente diverses méthodes parseFrom().
  • 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 de prototype. 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:

  1. Chaque trait de soulignement dans le nom est supprimé, et la lettre suivante est en majuscule.
  2. 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() : renvoie true 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() renvoie true et getFoo() renvoie value.
  • Builder clearFoo() : efface la valeur du champ. Après avoir appelé cela, hasFoo() renvoie false et getFoo() 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() renvoie value.
  • 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() : renvoie true 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 que ProtocolStringList. 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 champ Iterable 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): renvoie true si l'un des cas est FOO.
  • int getFoo() : renvoie la valeur actuelle de example_name si l'un des cas est FOO. 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éfinit example_name sur cette valeur et définit l'un des cas sur FOO. Après avoir appelé cela, hasFoo() renvoie true, getFoo() renvoie value et getExampleNameCase() renvoie FOO.
  • Builder clearFoo() :
    • Rien ne changera si la casse n'est pas FOO.
    • Si l'un des cas est FOO, définit example_name sur "null" et l'un sur EXAMPLENAME_NOT_SET. Après avoir appelé cela, hasFoo() renvoie false, getFoo() renvoie la valeur par défaut et getExampleNameCase() renvoie EXAMPLENAME_NOT_SET.

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 un Map 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 une Map 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 (ou null 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. Renvoie EXAMPLENAME_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 sur EXAMPLENAME_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 de forNumber(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 que valueOf(int). Dans proto3, la fonction renvoie UNRECOGNIZED 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éthode getDescriptorForType(), 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:

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 utilisez Foo.newReflectiveService(Foo.Interface) pour construire un Service 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 que package.MessageType.
  • builder_scope:TYPENAME : déclarations de membre qui appartiennent à la classe de compilateur d'un message. TYPENAME est le nom proto complet, tel que package.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 que package.EnumType.
  • message_implements:TYPENAME: déclarations d'implémentation de la classe pour un message. TYPENAME est le nom proto complet, tel que package.MessageType.
  • builder_implements:TYPENAME: déclarations d'implémentation de classe pour une classe de compilateur. TYPENAME est le nom proto complet, tel que package.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).