- Invocação do compilador
- Pacotes
- Mensagens
- Campos
- Tudo
- Um
- Enumerações
- Extensões
- Serviços
- Pontos de inserção do plug-in
Esta página descreve exatamente qual código Java o compilador de buffer de protocolo gera para qualquer definição de protocolo. Qualquer diferença entre o código gerado por proto2 e proto3 está destacada. Observe que essas diferenças estão no código gerado conforme descrito neste documento, não nas classes/interfaces de mensagem base, que são as mesmas nas duas versões. Leia o guia de idiomas do proto2 e/ou o guia do idioma do proto3 antes de ler este documento.
Observe que nenhum método Java do buffer de protocolo aceita ou retorna nulos, a menos que seja especificado de outra forma.
Invocação do compilador
O compilador de buffer de protocolo produz a saída Java quando invocada com a sinalização de linha de comando --java_out=
. O parâmetro para a opção --java_out=
é o diretório em que você quer que o compilador grave a saída do Java. Para cada entrada de arquivo .proto
, o compilador cria um arquivo .java
wrapper que contém uma classe Java que representa o próprio arquivo .proto
.
Se o arquivo .proto
contiver uma linha como a seguinte:
option java_multiple_files = true;
Em seguida, o compilador também cria um arquivo .java
separado para cada mensagem de nível superior, enumeração e serviço declarados no arquivo .proto
.
Caso contrário, quando a opção java_multiple_files
for "false" (o padrão), a classe de wrapper mencionada acima também será usada como uma classe externa, e as classes/enumerações geradas para cada mensagem de nível superior, enumeração e serviço declaradas no arquivo .proto
serão aninhadas dentro da classe de wrapper externo. Assim, o compilador só
gera um único arquivo .java
para todo o arquivo .proto
.
O nome da classe de wrapper é escolhido da seguinte maneira. Se o arquivo .proto
contiver uma linha como a seguinte:
option java_outer_classname = "Foo";
O nome da classe de wrapper será Foo
. Caso contrário, o nome da classe do wrapper é determinado pela conversão do nome base do arquivo .proto
em letras concatenadas. Por exemplo, foo_bar.proto
gerará um nome de classe de FooBar
. Se houver um serviço, uma enumeração ou uma mensagem (incluindo tipos aninhados) no arquivo com o mesmo nome, "OuterClass" vai ser anexado ao nome da classe do wrapper. Exemplos:
- Se
foo_bar.proto
contiver uma mensagem chamadaFooBar
, a classe de wrapper gerará um nome de classe deFooBarOuterClass
. - Se
foo_bar.proto
contiver um serviço chamadoFooService
ejava_outer_classname
também estiver definido como a stringFooService
, a classe de wrapper gerará um nome de classe deFooServiceOuterClass
.
Além de quaisquer classes aninhadas, a própria classe do wrapper terá a seguinte API, supondo que a classe do wrapper seja chamada Foo
e tenha sido gerada 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) }
O nome do pacote Java é escolhido conforme descrito em Pacotes abaixo.
O arquivo de saída é escolhido concatenando o parâmetro para --java_out=
, o nome do pacote (com .
s substituídos por /
s) e o nome do arquivo .java
.
Então, por exemplo, digamos que você invoque o compilador da seguinte maneira:
protoc --proto_path=src --java_out=build/gen src/foo.proto
Se o pacote Java do foo.proto
for com.example
e ele não ativar java_multiple_files
e o nome de classe externo dele for FooProtos
, o compilador de buffer de protocolo gerará o arquivo build/gen/com/example/FooProtos.java
. O compilador de buffer de protocolo criará automaticamente os diretórios build/gen/com
e build/gen/com/example
, se necessário. No entanto, ela não criará a build/gen
ou a build
. É necessário que elas já existam. É possível especificar vários arquivos .proto
em uma única invocação. Todos os arquivos de saída serão gerados de uma só vez.
Ao gerar código Java, a capacidade do compilador de buffer de protocolo de saída diretamente para arquivos JAR é particularmente conveniente, porque muitas ferramentas Java são capazes de ler código-fonte diretamente de arquivos JAR. Para gerar um arquivo JAR, forneça um local de saída terminado em .jar
. Apenas o código-fonte Java é colocado no arquivo. É necessário compilá-lo separadamente para produzir arquivos de classe Java.
Entrega de pacotes
A classe gerada é colocada em um pacote Java com base na opção java_package
. Se a opção for omitida, a declaração package
será usada.
Por exemplo, se o arquivo .proto
contiver:
package foo.bar;
Em seguida, a classe Java resultante será colocada no pacote Java foo.bar
. No entanto, se o arquivo .proto
também tiver uma opção java_package
, como:
package foo.bar; option java_package = "com.example.foo.bar";
Em seguida, a classe é colocada no pacote com.example.foo.bar
. A opção java_package
é fornecida porque declarações .proto
package
normais não devem começar com um nome de domínio reverso.
Mensagens
Se a declaração for simples:
message Foo {}
O compilador de buffer de protocolo gera uma classe chamada Foo
, que implementa a interface Message
. A classe é declarada como final
. Nenhuma outra subclasse é permitida. Foo
estende GeneratedMessage
, mas isso deve ser considerado um detalhe de implementação. Por padrão, o Foo
modifica muitos métodos de GeneratedMessage
por versões especializadas para alcançar a velocidade máxima. No entanto, se o arquivo .proto
contiver a linha:
option optimize_for = CODE_SIZE;
o Foo
vai substituir apenas o conjunto mínimo de métodos necessários para funcionar e confiar nas implementações baseadas em reflexão do GeneratedMessage
do restante. Isso reduz significativamente o tamanho do código gerado, mas também reduz o desempenho.
Como alternativa, se o diretório de saída Java usado pelo protoc for prefixado com lite:
,
por exemplo, --java_out=lite:project/protos
,
Foo
incluirá implementações rápidas de todos os métodos, mas implementará a
interface MessageLite
,
que contém apenas um subconjunto dos métodos de Message
. Mais especificamente,
ele não é compatível com descritores ou reflexão. No entanto, nesse modo, o código gerado só
precisa ser vinculado a libprotobuf-lite.jar
em vez de libprotobuf.jar
.
A biblioteca "lite" é muito menor que a biblioteca completa e é mais adequada para
sistemas com recursos limitados, como smartphones.
A interface Message
define métodos que permitem verificar, manipular, ler ou gravar a mensagem inteira. Além desses métodos, a classe Foo
define os seguintes métodos estáticos:
static Foo getDefaultInstance()
: retorna a instância singleton deFoo
. O conteúdo dessa instância será idêntico ao que você receberia se chamasseFoo.newBuilder().build()
. Assim, todos os campos únicos não estão definidos e todos os campos repetidos estão vazios. A instância padrão de uma mensagem pode ser usada como fábrica chamando o métodonewBuilderForType()
.static Descriptor getDescriptor()
: retorna o descritor do tipo. Ela contém informações sobre o tipo, incluindo quais campos ele tem e quais são os tipos. Isso pode ser usado com os métodos de reflexão doMessage
, comogetField()
.static Foo parseFrom(...)
: analisa uma mensagem do tipoFoo
da origem fornecida e a retorna. Há um métodoparseFrom
correspondente a cada variante demergeFrom()
na interfaceMessage.Builder
. AparseFrom()
nunca geraUninitializedMessageException
. Ela geraInvalidProtocolBufferException
se a mensagem analisada não tem campos obrigatórios. Isso o torna um pouco diferente da chamada deFoo.newBuilder().mergeFrom(...).build()
.static Parser parser()
: retorna uma instância doParser
, que implementa vários métodosparseFrom()
.Foo.Builder newBuilder()
: cria um novo builder (descrito abaixo).Foo.Builder newBuilder(Foo prototype)
: cria um novo builder com todos os campos inicializados com os mesmos valores que emprototype
. Como os objetos de string e mensagem incorporados são imutáveis, eles são compartilhados entre o original e a cópia.
Criadores
Os objetos de mensagem, como instâncias da classe Foo
descrita acima, são imutáveis, assim como um String
do Java. Para criar um objeto de mensagem, você precisa usar um builder. Cada classe de mensagem tem a própria classe builder. Portanto, no exemplo do Foo
, o compilador do buffer de protocolo gera uma classe aninhada Foo.Builder
, que pode ser usada para criar uma Foo
. Foo.Builder
implementa a interface Message.Builder
. Ela estende a classe GeneratedMessage.Builder
, mas, mais uma vez, isso deve ser considerado um detalhe de implementação. Assim como a Foo
, a Foo.Builder
pode depender de implementações de método genérico na GeneratedMessage.Builder
ou, quando a opção optimize_for
é usada, gerar um código personalizado muito mais rápido.
Foo.Builder
não define métodos estáticos. A interface dela é exatamente como definido pela interface Message.Builder
, com a exceção de que os tipos de retorno são mais específicos: métodos de Foo.Builder
que modificam o tipo de retorno do builder Foo.Builder
e build()
retorna o tipo Foo
.
Os métodos que modificam o conteúdo de um builder, incluindo setters de campo, sempre retornam uma referência ao builder. Ou seja, "return this;
".
Isso permite que várias chamadas de método sejam encadeadas em uma linha. Por exemplo:
builder.mergeFrom(obj).setFoo(1).setBar("abc").clearBaz();
Como os builders não são seguros para linhas de execução, a sincronização Java precisa ser usada sempre que for necessário que várias linhas de execução diferentes modifiquem o conteúdo de um único builder.
Subcriadores
Para mensagens que contêm submensagens, o compilador também gera subbuilders. Isso permite modificar repetidamente as submensagens aninhadas sem recriá-las. Por exemplo:
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. }
Se você já tem uma mensagem Baz
e quer mudar o val
aninhado em Foo
. Em vez de:
baz = baz.toBuilder().setBar( baz.getBar().toBuilder().setFoo( baz.getBar().getFoo().toBuilder().setVal(10).build() ).build()).build();
É possível escrever:
Baz.Builder builder = baz.toBuilder(); builder.getBarBuilder().getFooBuilder().setVal(10); baz = builder.build();
Tipos aninhados
Uma mensagem pode ser declarada dentro de outra. Por exemplo:
message Foo {
message Bar {
}
}
Nesse caso, o compilador simplesmente gera Bar
como uma classe interna aninhada em Foo
.
Campos
Além dos métodos descritos na seção anterior, o compilador de buffer de protocolo gera um conjunto de métodos do acessador para cada campo definido na mensagem no arquivo .proto
. Os métodos que leem o valor do campo são definidos tanto na classe da mensagem quanto no builder correspondente. Os métodos que modificam o valor são definidos apenas no builder.
Os nomes de métodos sempre usam letras concatenadas, mesmo que o nome do campo no arquivo .proto
use letras minúsculas com sublinhados (como deve ser feito). A conversão de caso funciona da seguinte maneira:
- Para cada sublinhado no nome, o sublinhado é removido e a letra a seguir é maiúscula.
- Se o nome tiver um prefixo como "get", a primeira letra é maiúscula. Caso contrário, ela está em letras minúsculas.
Assim, o campo foo_bar_baz
se torna fooBarBaz
. Se prefixado com get
, seria getFooBarBaz
.
Em alguns casos especiais, em que um nome de método entra em conflito com palavras reservadas em Java
ou métodos já definidos na biblioteca protobuf, um sublinhado extra é anexado.
Por exemplo, o getter de um campo chamado class
é getClass_
para evitar conflito com o método getClass
em java.lang.Object
.
Além dos métodos de acessador, o compilador gera uma constante de número inteiro para cada campo que contém o número do campo. O nome da constante é o nome do campo convertido em maiúsculas e depois _FIELD_NUMBER
. Por exemplo, dado o campo optional int32 foo_bar = 5;
, o compilador vai gerar a constante public static final int FOO_BAR_FIELD_NUMBER = 5;
.
Campos Singular (proto2)
Para qualquer uma dessas definições de campo:
optional int32 foo = 1; required int32 foo = 1;
O compilador vai gerar os seguintes métodos de acessado na classe da mensagem e no builder:
boolean hasFoo()
: retornatrue
se o campo estiver definido.int getFoo()
: retorna o valor atual do campo. Se o campo não for definido, retornará o valor padrão.
O compilador vai gerar os seguintes métodos apenas no builder da mensagem:
Builder setFoo(int value)
: define o valor do campo. Depois disso,hasFoo()
retornarátrue
, egetFoo()
retornarávalue
.Builder clearFoo()
: limpa o valor do campo. Depois de chamar isso,hasFoo()
retornaráfalse
, egetFoo()
retornará o valor padrão.
Para outros tipos de campo simples, o tipo Java correspondente é escolhido de acordo com a tabela de tipos de valores escalares. Nos tipos de mensagem e enum, o tipo de valor é substituído pela mensagem ou pela classe de enumeração.
Campos de mensagens incorporados
Para tipos de mensagem, setFoo()
também aceita uma instância do tipo de criador da mensagem como parâmetro. Esse é apenas um atalho equivalente a chamar .build()
no builder e transmitir o resultado para o método.
Se o campo não for definido, getFoo()
retornará uma instância de Foo sem um dos campos definidos (possivelmente a instância retornada por Foo.getDefaultInstance()
).
Além disso, o compilador gera dois métodos de acesso que permitem acessar os subbuilders relevantes para os tipos de mensagem. O método a seguir é gerado na classe da mensagem e no builder:
FooOrBuilder getFooOrBuilder()
: retorna o builder do campo, se já existir, ou a mensagem, se não existir. Chamar esse método em builders não criará um subbuilder para o campo.
O compilador gera o seguinte método apenas no builder da mensagem.
Builder getFooBuilder()
: retorna o builder do campo.
Campos Singular (proto3)
Para esta definição de campo:
int32 foo = 1;
O compilador vai gerar o seguinte método de acessado na classe da mensagem e no builder:
int getFoo()
: retorna o valor atual do campo. Se o campo não estiver definido, retorna o valor padrão para o tipo de campo.
O compilador vai gerar os seguintes métodos apenas no builder da mensagem:
Builder setFoo(int value)
: define o valor do campo. Depois de chamar isso,getFoo()
retornarávalue
.Builder clearFoo()
: limpa o valor do campo. Depois de chamar isso,getFoo()
retornará o valor padrão para o tipo de campo.
Para outros tipos de campo simples, o tipo Java correspondente é escolhido de acordo com a tabela de tipos de valores escalares. Nos tipos de mensagem e enum, o tipo de valor é substituído pela mensagem ou pela classe de enumeração.
Campos de mensagens incorporados
Para tipos de campo de mensagem, um método de acessador adicional é gerado na classe de mensagem e no builder:
boolean hasFoo()
: retornarátrue
se o campo tiver sido definido.
setFoo()
também aceita uma instância do tipo de builder da mensagem como parâmetro. Esse é apenas um atalho equivalente a chamar .build()
no builder e transmitir o resultado para o método.
Se o campo não for definido, getFoo()
retornará uma instância de Foo sem um dos campos definidos (possivelmente a instância retornada por Foo.getDefaultInstance()
).
Além disso, o compilador gera dois métodos de acesso que permitem acessar os subbuilders relevantes para os tipos de mensagem. O método a seguir é gerado na classe da mensagem e no builder:
FooOrBuilder getFooOrBuilder()
: retorna o builder do campo, se já existir, ou a mensagem, se não existir. Chamar esse método em builders não criará um subbuilder para o campo.
O compilador gera o seguinte método apenas no builder da mensagem.
Builder getFooBuilder()
: retorna o builder do campo.
Campos de enumeração
Para tipos de campo de enumeração, um método do acessador adicional é gerado na classe da mensagem e no builder:
int getFooValue()
: retorna o valor inteiro da enumeração.
O compilador vai gerar o seguinte método adicional somente no builder da mensagem:
Builder setFooValue(int value)
: define o valor inteiro da enumeração.
Além disso, getFoo()
retornará UNRECOGNIZED
se o valor da enumeração for desconhecido. Esse é um valor adicional especial adicionado pelo compilador proto3 ao tipo de enumeração gerado.
Campos repetidos
Para esta definição de campo:
repeated string foo = 1;
O compilador vai gerar os seguintes métodos de acessado na classe da mensagem e no builder:
int getFooCount()
: retorna o número de elementos atualmente em campo.String getFoo(int index)
: retorna o elemento no índice com base em zero especificado.ProtocolStringList getFooList()
: retorna todo o campo comoProtocolStringList
. Se o campo não for definido, retornará uma lista vazia.
O compilador vai gerar os seguintes métodos apenas no builder da mensagem:
Builder setFoo(int index, String value)
: define o valor do elemento no índice baseado em zero específico.Builder addFoo(String value)
: anexa um novo elemento ao campo com o valor informado.Builder addAllFoo(Iterable<? extends String> value)
: anexa todos os elementos noIterable
especificado ao campo.Builder clearFoo()
: remove todos os elementos do campo. Depois de chamar isso,getFooCount()
retornará zero.
Para outros tipos de campo simples, o tipo Java correspondente é escolhido de acordo com a tabela de tipos de valores escalares. Para mensagens e tipos de enumeração, o tipo é a mensagem ou a classe de enumeração.
Campos repetidos de mensagem incorporada
Para tipos de mensagem, setFoo()
e addFoo()
também aceitam uma instância do tipo de builder da mensagem como o parâmetro. Esse é apenas um atalho equivalente a chamar .build()
no builder e transmitir o resultado para o método. Há também um método gerado adicional:
Builder addFoo(int index, Field value)
: insere um novo elemento no índice baseado em zero específico. Desloca o elemento que está atualmente naquela posição (se houver) e os elementos subsequentes para a direita (adiciona um ao índice da pessoa).
Além disso, o compilador gera os seguintes métodos extras de acesso na classe da mensagem e no builder para tipos de mensagem, permitindo que você acesse os subconstrutores relevantes:
FooOrBuilder getFooOrBuilder(int index)
: retorna o builder do elemento especificado, se já existir, ou o elemento em caso negativo. Se for chamada em uma classe de mensagem, ela sempre retornará uma mensagem em vez de um builder. Chamar esse método em builders não criará um subbuilder para o campo.List<FooOrBuilder> getFooOrBuilderList()
: retorna todo o campo como uma lista não modificável de builders (se disponível) ou mensagens se não estiver. Se ela for chamada em uma classe de mensagem, ela sempre retornará uma lista imutável de mensagens em vez de uma lista não modificável de builders.
O compilador vai gerar os seguintes métodos apenas no builder da mensagem:
Builder getFooBuilder(int index)
: retorna o builder do elemento no índice especificado.Builder addFooBuilder(int index)
: insere e retorna um builder para uma instância de mensagem padrão no índice especificado. As entradas atuais são movidas para índices mais altos a fim de criar espaço para o builder inserido.Builder addFooBuilder()
: anexa e retorna um builder para uma instância de mensagem padrão.Builder removeFoo(int index)
: remove o elemento no índice baseado em zero específico.List<Builder> getFooBuilderList()
: retorna todo o campo como uma lista não modificável de builders.
Campos de enumeração repetidos (somente proto3)
O compilador vai gerar os seguintes métodos adicionais na classe da mensagem e no builder:
int getFooValue(int index)
: retorna o valor inteiro da enumeração no índice especificado.List<java.lang.Integer> getFooValueList()
: retorna o campo inteiro como uma lista de números inteiros.
O compilador vai gerar o seguinte método adicional somente no builder da mensagem:
Builder setFooValue(int index, int value)
: define o valor inteiro da enumeração no índice especificado.
Conflitos de nomes
Se outro campo não repetido tiver um nome conflitante com um dos métodos gerados, os dois nomes terão o número protobuf anexado ao final.
Para estas definições de campo:
int32 foo_count = 1; repeated string foo = 2;
Primeiro, o compilador os renomeará da seguinte forma:
int32 foo_count_1 = 1; repeated string foo_2 = 2;Os métodos do acessador serão gerados conforme descrito acima.
Oneof Fields
Para esta definição de campo única:
oneof example_name { int32 foo = 1; ... }
O compilador vai gerar os seguintes métodos de acessado na classe da mensagem e no builder:
boolean hasFoo()
(somente proto2): retornatrue
se o um dos casos forFOO
.int getFoo()
: retorna o valor atual deexample_name
se o caso único forFOO
. Caso contrário, retorna o valor padrão deste campo.
O compilador vai gerar os seguintes métodos apenas no builder da mensagem:
Builder setFoo(int value)
: defineexample_name
como esse valor e define o único caso comoFOO
. Depois disso,hasFoo()
retornarátrue
,getFoo()
retornarávalue
egetExampleNameCase()
retornaráFOO
.Builder clearFoo()
:- Nada vai mudar se o caso não for
FOO
. - Se o caso um for
FOO
, vai definirexample_name
como nulo e o único comoEXAMPLENAME_NOT_SET
. Depois de chamar esse método,hasFoo()
retornaráfalse
,getFoo()
retornará o valor padrão egetExampleNameCase()
retornaráEXAMPLENAME_NOT_SET
.
- Nada vai mudar se o caso não for
Para outros tipos de campo simples, o tipo Java correspondente é escolhido de acordo com a tabela de tipos de valores escalares. Nos tipos de mensagem e enum, o tipo de valor é substituído pela mensagem ou pela classe de enumeração.
Campos do mapa
Para esta definição de campo do mapa:
map<int32, int32> weight = 1;
O compilador vai gerar os seguintes métodos de acessado na classe da mensagem e no builder:
Map<Integer, Integer> getWeightMap();
: retorna umMap
não modificável.int getWeightOrDefault(int key, int default);
: retorna o valor da chave ou o valor padrão, se ausente.int getWeightOrThrow(int key);
: retorna o valor da chave ou gera uma IllegalArgumentException se não houver presença.boolean containsWeight(int key);
: indica se a chave está presente nesse campo.int getWeightCount();
: retorna o número de elementos no mapa.
O compilador vai gerar os seguintes métodos apenas no builder da mensagem:
Builder putWeight(int key, int value);
: adicione o peso a este campo.Builder putAllWeight(Map<Integer, Integer> value);
: adiciona todas as entradas no mapa a este campo.Builder removeWeight(int key);
: remove o peso deste campo.Builder clearWeight();
: remove todos os pesos deste campo.@Deprecated Map<Integer, Integer> getMutableWeight();
: retorna umMap
mutável. Várias chamadas para esse método podem retornar diferentes instâncias de mapa. A referência do mapa retornada pode ser invalidada por qualquer chamada de método subsequente para o Builder.
Qualquer
Dado um campo Any
como este:
import "google/protobuf/any.proto"; message ErrorStatus { string message = 1; google.protobuf.Any details = 2; }
No código gerado, o getter para o campo details
retorna uma instância de com.google.protobuf.Any
. Isso fornece os seguintes métodos especiais para empacotar e descompactar os valores de 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; }
Oneof
Considere uma das seguintes definições:
oneof example_name { int32 foo_int = 4; string foo_string = 9; ... }
Todos os campos na example_name
usarão um único campo particular como valor. Além disso, o compilador de buffer de protocolo gerará um tipo de enumeração Java para o caso um, da seguinte maneira:
public enum ExampleNameCase implements com.google.protobuf.Internal.EnumLite { FOO_INT(4), FOO_STRING(9), ... EXAMPLENAME_NOT_SET(0); ... };
Os valores desse tipo de enumeração têm os seguintes métodos especiais:
int getNumber()
: retorna o valor numérico do objeto, conforme definido no arquivo .proto.static ExampleNameCase forNumber(int value)
: retorna o objeto enum correspondente ao valor numérico fornecido (ounull
para outros valores numéricos).
O compilador também vai gerar o seguinte método de acessado na classe da mensagem e no builder:
ExampleNameCase getExampleNameCase()
: retorna a enumeração indicando qual campo está definido. RetornaráEXAMPLENAME_NOT_SET
se nenhum deles estiver definido.
O compilador vai gerar o seguinte método apenas no builder da mensagem:
Builder clearExampleName()
: redefine o campo particular oneof como nulo e o define comoEXAMPLENAME_NOT_SET
.
Enumerações
Considerando uma definição de enumeração como:
enum Foo { A = 0; B = 5; C = 1234; }
O compilador de buffer de protocolo gerará um tipo de enumeração Java chamado Foo
com o mesmo conjunto de valores. Se você estiver usando o proto3, ele também adicionará o valor especial UNRECOGNIZED
ao tipo de enumeração. Os valores do tipo de enumeração gerado têm os seguintes métodos especiais:
int getNumber()
: retorna o valor numérico do objeto, conforme definido no arquivo.proto
.EnumValueDescriptor getValueDescriptor()
: retorna o descritor do valor, que contém informações sobre nome, número e tipo.EnumDescriptor getDescriptorForType()
: retorna o descritor do tipo enum, que contém informações sobre cada valor definido.
Além disso, o tipo de enumeração Foo
contém os seguintes métodos estáticos:
static Foo forNumber(int value)
: retorna o objeto enum correspondente ao valor numérico fornecido. Retorna nulo quando não há um objeto de enumeração correspondente.static Foo valueOf(int value)
: retorna o objeto enum correspondente ao valor numérico fornecido. Esse método foi descontinuado e substituído porforNumber(int value)
. Ele será removido em uma versão futura.static Foo valueOf(EnumValueDescriptor descriptor)
: retorna o objeto enum correspondente ao descritor de valor fornecido. Pode ser mais rápido quevalueOf(int)
. No proto3, retornaUNRECOGNIZED
se receber um descritor de valor desconhecido.EnumDescriptor getDescriptor()
: retorna o descritor do tipo enum, que contém informações sobre cada valor definido. Isso difere degetDescriptorForType()
, porque é um método estático.
Uma constante de número inteiro também é gerada com o sufixo _VALUE para cada valor de enumeração.
O idioma .proto
permite que vários símbolos de enumeração tenham o mesmo valor numérico. Símbolos com o mesmo valor numérico são sinônimos. Por exemplo:
enum Foo { BAR = 0; BAZ = 0; }
Nesse caso, BAZ
é sinônimo de BAR
. Em Java, BAZ
será definido como um campo final estático, como este:
static final Foo BAZ = BAR;
Assim, BAR
e BAZ
são comparados iguais, e BAZ
nunca deve aparecer em instruções de alternância. O compilador sempre escolhe o primeiro símbolo definido com um determinado valor numérico como a versão "canônica" desse símbolo. Todos os símbolos subsequentes com o mesmo número são apenas aliases.
Uma enumeração pode ser definida aninhada em um tipo de mensagem. O compilador gera a definição de enumeração Java aninhada na classe desse tipo de mensagem.
Extensões (somente proto2)
Você recebeu uma mensagem com um intervalo de extensões:
message Foo { extensions 100 to 199; }
O compilador de buffer de protocolo fará com que Foo
estenda GeneratedMessage.ExtendableMessage
em vez do GeneratedMessage
normal. Da mesma forma, o builder da Foo
vai estender o GeneratedMessage.ExtendableBuilder
. Nunca se refere a esses tipos de base pelo nome (GeneratedMessage
é considerado um detalhe de implementação). No entanto, essas superclasses definem vários métodos adicionais que podem ser usados para manipular extensões.
Em especial, Foo
e Foo.Builder
vão herdar os métodos hasExtension()
, getExtension()
e getExtensionCount()
. Além disso, Foo.Builder
herdará os métodos setExtension()
e clearExtension()
. Cada um desses métodos usa, como seu primeiro parâmetro, um identificador de extensão (descrito abaixo), que identifica um campo de extensão. Os parâmetros restantes e o valor de retorno são exatamente os mesmos dos métodos do acessador correspondentes que seriam gerados para um campo normal (sem extensão) do mesmo tipo do identificador da extensão.
Com uma definição de extensão:
extend Foo { optional int32 bar = 123; }
O compilador de buffer de protocolo gera um "identificador de extensão" chamado bar
, que você pode usar com os acessadores de extensão de Foo
para acessar esta extensão, desta forma:
Foo foo = Foo.newBuilder() .setExtension(bar, 1) .build(); assert foo.hasExtension(bar); assert foo.getExtension(bar) == 1;
A implementação exata dos identificadores de extensão é complicada e envolve o uso mágico de genéricos. No entanto, você não precisa se preocupar com o modo como os identificadores de extensão funcionam para usá-los.
Observe que bar
seria declarado como um campo estático da classe de wrapper do arquivo .proto
, conforme descrito acima. O nome da classe de wrapper foi omitido no exemplo.
As extensões podem ser declaradas dentro do escopo de outro tipo para prefixar os nomes de símbolos gerados. Por exemplo, um padrão comum é estender uma mensagem por um campo dentro da declaração do tipo do campo:
message Baz { extend Foo { optional Baz foo_ext = 124; } }
Nesse caso, uma extensão com identificador foo_ext
e tipo Baz
é declarada na declaração de Baz
. Para se referir a foo_ext
, é necessário adicionar o prefixo Baz.
:
Baz baz = createMyBaz(); Foo foo = Foo.newBuilder() .setExtension(Baz.fooExt, baz) .build(); assert foo.hasExtension(Baz.fooExt); assert foo.getExtension(Baz.fooExt) == baz;
Ao analisar uma mensagem que pode ter extensões, você precisa fornecer um ExtensionRegistry no qual já registrou as extensões que quer analisar. Caso contrário, essas extensões serão tratadas como campos desconhecidos, e os métodos que observam as extensões se comportarão como se não existissem.
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;
Serviços
Se o arquivo .proto
contiver a seguinte linha:
option java_generic_services = true;
Em seguida, o compilador de buffer de protocolo gerará o código com base nas definições de serviço encontradas no arquivo, conforme descrito nesta seção. No entanto, o código gerado pode ser indesejável, porque não está vinculado a nenhum sistema RPC específico e, portanto, exige mais níveis de indireção do que o código personalizado para um sistema. Se você NÃO quiser que esse código seja gerado, adicione esta linha ao arquivo:
option java_generic_services = false;
Se nenhuma das linhas acima for fornecida,
a opção padrão será false
, já que o uso de serviços genéricos foi descontinuado.
Observe que, antes de 2.4.0, a opção padrão é true
.
Os sistemas de RPC baseados em definições de serviço da linguagem .proto
precisam fornecer plug-ins para gerar o código adequado para o sistema. Esses plug-ins provavelmente exigem
que os serviços abstratos sejam desativados para que possam gerar as próprias
classes com os mesmos nomes. Os plug-ins são novos na versão 2.3.0 (janeiro de 2010).
O restante desta seção descreve o que o compilador de buffer de protocolo gera quando os serviços abstratos são ativados.
Interface
Com uma definição de serviço:
service Foo { rpc Bar(FooRequest) returns(FooResponse); }
O compilador de buffer de protocolo gerará uma classe abstrata Foo
para representar esse serviço. O Foo
vai ter um método abstrato para cada método definido na definição de serviço. Nesse caso, o método Bar
é definido como:
abstract void bar(RpcController controller, FooRequest request, RpcCallback<FooResponse> done);
Os parâmetros são equivalentes aos parâmetros de Service.CallMethod()
, exceto que o argumento method
está implícito e que request
e done
especificam o tipo exato deles.
Foo
cria uma subclasse da interface Service
. O compilador de buffer de protocolo gera automaticamente implementações dos métodos de Service
da seguinte maneira:
getDescriptorForType
: retorna oServiceDescriptor
do serviço.callMethod
: determina qual método está sendo chamado com base no descritor de método fornecido e o chama diretamente, transmitindo a mensagem de solicitação e o callback para os tipos corretos.getRequestPrototype
egetResponsePrototype
: retorna a instância padrão da solicitação ou resposta do tipo correto do método especificado.
O seguinte método estático também é gerado:
static ServiceDescriptor getServiceDescriptor()
: retorna o descritor do tipo, que contém informações sobre quais métodos esse serviço tem e quais são os tipos de entrada e saída.
A Foo
também conterá uma interface aninhada Foo.Interface
. Essa é uma interface pura que contém novamente os métodos correspondentes a cada método na sua definição de serviço. No entanto, essa interface não estende a Service
. Isso é um problema porque as implementações do servidor RPC geralmente são escritas para usar objetos Service
abstratos, e não o serviço específico. Para resolver esse problema, se você tiver um objeto impl
implementando Foo.Interface
, chame Foo.newReflectiveService(impl)
para construir uma instância de Foo
que simplesmente delega impl
e implementa Service
.
Em resumo, ao implementar seu próprio serviço, você tem duas opções:
- Subclassifique
Foo
e implemente os métodos dela conforme apropriado. Em seguida, coloque as instâncias da subclasse diretamente na implementação do servidor RPC. Isso geralmente é mais fácil, mas alguns consideram isso menos "puro". - Implemente
Foo.Interface
e useFoo.newReflectiveService(Foo.Interface)
para construir umService
unindo-o. Em seguida, transmita o wrapper para sua implementação de RPC.
Stub
O compilador de buffer de protocolo também gera uma implementação de stub de cada interface de serviço, que é usada por clientes que querem enviar solicitações para servidores que implementam o serviço. Para o serviço Foo
(acima), a implementação de stub Foo.Stub
será definida como uma classe aninhada.
A Foo.Stub
é uma subclasse da Foo
que também implementa os métodos abaixo:
Foo.Stub(RpcChannel channel)
: cria um novo stub que envia solicitações sobre o canal.RpcChannel getChannel()
: retorna o canal do stub, conforme transmitido ao construtor.
O stub também implementa cada um dos métodos do serviço como um wrapper em torno do canal. Chamar um dos métodos simplesmente chama channel.callMethod()
.
A biblioteca de buffers de protocolo não inclui uma implementação de RPC. No entanto, ele inclui todas as ferramentas necessárias para conectar uma classe de serviço gerada a qualquer implementação de RPC arbitrária. Você só precisa fornecer implementações de RpcChannel
e RpcController
.
Como bloquear interfaces
As classes RPC descritas acima têm semânticas sem bloqueio: ao chamar um método, você fornece um objeto de callback que será invocado quando o método for concluído. Muitas vezes, é mais fácil (embora possivelmente menos escalonável) escrever código usando semântica de bloqueio, em que o método simplesmente não retorna até que seja concluído. Para acomodar isso, o compilador de buffer de protocolo também gera versões de bloqueio da classe de serviço. Foo.BlockingInterface
é equivalente a Foo.Interface
, mas cada método simplesmente retorna o resultado em vez de chamar um callback. Por exemplo, bar
é definido como:
abstract FooResponse bar(RpcController controller, FooRequest request) throws ServiceException;
Semelhante a serviços sem bloqueio, Foo.newReflectiveBlockingService(Foo.BlockingInterface)
retorna um BlockingService
encapsulando alguns Foo.BlockingInterface
. Por fim, Foo.BlockingStub
retorna uma implementação de stub de Foo.BlockingInterface
que envia solicitações para um BlockingRpcChannel
específico.
Pontos de inserção do plug-in
Os plug-ins de gerador de código, que querem estender a saída do gerador de código Java, podem inserir o código dos tipos abaixo usando os nomes dos pontos de inserção fornecidos.
outer_class_scope
: declarações de membro que pertencem à classe de wrapper do arquivo.class_scope:TYPENAME
: declarações de membro que pertencem a uma classe de mensagem.TYPENAME
é o nome completo do proto, comopackage.MessageType
.builder_scope:TYPENAME
: declarações de membro que pertencem à classe builder da mensagem.TYPENAME
é o nome completo do proto, comopackage.MessageType
.enum_scope:TYPENAME
: declarações de membro que pertencem a uma classe de enumeração.TYPENAME
é o nome do tipo de proto completo, comopackage.EnumType
.message_implements:TYPENAME
: declarações de implementação de classe para uma classe de mensagem.TYPENAME
é o nome completo do proto, comopackage.MessageType
.builder_implements:TYPENAME
: declarações de implementação de classe para uma classe builder.TYPENAME
é o nome completo do proto, comopackage.MessageType
.
O código gerado não pode conter instruções de importação, porque elas são propensas a entrar em conflito com nomes de tipos definidos no próprio código. Em vez disso, ao se referir a uma classe externa, você precisa usar sempre o nome totalmente qualificado dela.
A lógica para determinar os nomes dos arquivos de saída no gerador de código Java é bastante complicada. Você provavelmente deve analisar o código-fonte protoc
, especialmente java_headers.cc
, para garantir que tenha abordado todos os casos.
Não gere código que dependa de membros de classe privada declarados pelo gerador de código padrão, porque esses detalhes de implementação podem mudar em versões futuras dos buffers de protocolo.
Aulas de utilitários
O buffer de protocolo fornece classes de utilitário para comparação de mensagens, conversão JSON e para trabalhar com tipos conhecidos (mensagens de buffer de protocolo predefinidas para casos de uso comuns).