Java Generated Code

本页面描述了协议缓冲区编译器为任何给定协议定义生成的Java代码。proto2和proto3生成的代码之间的任何区别都会突出显示 - 请注意,
这些差异是指生成代码中的差异,而不是两个版本中相同的基本消息类/接口。在阅读本文档之前,您应该阅读
proto2语言指南和/或proto3语言指南

编译器调用

当使用--java_out=命令行标志调用时,协议缓冲区编译器生成Java代码。`–java_out=`可选参数是您希望编译器存放Java类的目录。编译器为每个.proto文件输入创建一个.java
该文件包含单个外部类定义,其中包含基于.proto文件中的声明的多个内部类和静态字段。

外部类的名称如下所示:(如果.proto文件包含如下所示的行)

option java_outer_classname = "Foo";

那么外部类的名字将是Foo。否则,外部类名通过将.proto文件基础名称转换为骆驼命名来确定。例如,foo_bar.proto将会命名为FooBar。如果文件中有相同名称的消息,OuterClass将作为附加到外部类的名称后缀。
例如,如果foo_bar.proto包含一个名为FooBar的消息,则外部类将成为FooBarOuterClass

通过将参数连接到--java_out=来选择输出文件,软件包名称(.s替换为/s)和.java文件名。

所以,举个例子,假设你像下面这样调用编译器:

protoc --proto_path=src --java_out=build/gen src/foo.proto

如果foo.proto的java包是com.example,其外部类名是FooProtos,那么协议缓冲区编译器将生成文件build/gen/com/example/FooProtos.java
如果需要,协议缓冲区编译器将自动创建build/gen/combuild/gen/com/example目录。但是,它不会创建build/gen或build目录;它们必须存在。
您可以在单个调用中指定多个.proto文件;所有输出文件将一次生成。

输出Java代码时,协议缓冲区编译器直接输出到JAR包的能力特别方便,许多Java工具能够直接从JAR文件读取源代码。要输出到JAR文件,只需提供以.jar结尾的输出位置。
请注意,只有Java源代码被放置在存档中;您仍然必须单独编译以生成Java .class文件

生成的类被放置在基于java_package选项的Java包中。如果省略该选项,则使用package声明。

例如,如果.proto文件包含:

package foo.bar;

那么生成的Java类将被放置在foo.bar包中。但是,如果.proto文件也包含一个java_package选项,就像这样:

package foo.bar;
option java_package = "com.example.foo.bar";

那么生成的Java类将被放置在com.example.foo.bar包中。建议提供了java_package选项,因为普通的.proto包声明不会以倒置的域名开头。

消息

给出一个简单的消息声明:

message Foo {}

协议缓冲区编译器生成一个名为Foo的类,它实现了Message接口。该类被声明成final类型;不允许进一步的子类化。
Foo继承GeneratedMessage,但这应该被视为一个实现细节。默认情况下,Foo使用专用版本来覆盖GeneratedMessage的许多方法,以实现最大速率。但是,如果.proto文件包含以下行:

option optimize_for = CODE_SIZE;

那么Foo将仅覆盖功能必需的最小的一组方法和其他的仍依赖于GeneratedMessage的基于反射的实现。这显著减少了生成代码的大小,但也降低了性能。或者,如果`.proto`文件包含:

option optimize_for = LITE_RUNTIME;

那么Foo将包括所有方法的快速实现,但将实现MessageLite接口,也将只包含Message所有方法的一个子集。特别是它不支持描述符或反射。但是,在这种模式下,生成的代码只需要引入libprotobuf-lite.jar而不是libprotobuf.jar
“lite”类库比完整的类库要小得多,更适合资源有限的系统,如手机。

Message接口定义了允许您检查,操作,读取或写入整个消息的方法。除了这些方法,Foo类还定义了以下静态方法:

  • static Foo getDefaultInstance(): 返回一个Foo的单例,它与您调用Foo.newBuilder().build()(即所有单个字段未设置,所有重复字段都为空)返回结果相同。
    注意,一个消息的默认实例是通过调用它的工厂方法newBuilderForType()

  • static Descriptor getDescriptor(): 返回类型的描述符。这包含有关该类型的信息,包括它有哪些字段以及它们的类型。这可以与Message的反射方法一起使用,例如getField()

  • static Foo parseFrom(…): 从给定的源解析一个类型为Foo的消息并返回。还有一个parseFrom方法,对应于Message.Builder接口中mergeFrom()的每个变体。请注意,parseFrom()不会抛出UninitializedMessageException;
    如果解析的消息缺少必填字段,它将抛出InvalidProtocolBufferException
    这使得它与调用Foo.newBuilder().mergeFrom(...).build()有微妙的不同。

  • static Parser parser(): 返回Parser的一个实例,它实现各种parseFrom()方法。

  • Foo.Builder newBuilder(): 创建一个新的构建器(如下所述)。

  • Foo.Builder newBuilder(Foo prototype): 创建一个新的构建器,其中所有字段都初始化为与原型相同的值。由于内嵌消息和字符串对象是不可变的,因此它们在原始和副本之间共享。

构建器

消息对象(如上述Foo类的实例)是不可变的,就像Java String一样。要构建消息对象,您需要使用构建器。每个消息类都有自己的构建器类 - 所以在我们的Foo示例中,协议缓冲区编译器生成一个可以用于构建Foo的内部类Foo.Builder
Foo.Builder实现Message.Builder接口。
它继承了GeneratedMessage.Builder类,但是,再次,这应该被认为是一个实现细节。
Foo一样,Foo.Builder可能依赖GeneratedMessage.Builder中的泛型方法实现,或者在使用optimize_for选项时,生成的自定义代码要快得多。

Foo.Builder没有定义任何静态方法。它的接口与Message.Builder接口的定义完全相同。但返回类型更具体:Foo.Builder的方法修改构建器返回类型Foo.Builder,而build()返回类型为Foo。

修改构建器(包括字段设置器)内的方法始终返回对构建器的引用。这允许多个方法链式调用。例如:builder.mergeFrom(obj).setFoo(1).setBar("abc").clearBaz();

子构建器

对于包含子消息的消息,编译器还生成子构建器。这允许您重复修改深层嵌套的子消息,而无需重新构建它们。例如:

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.
}

如果您已经有一个Baz消息,并且想要更改Foo中的深​​层嵌套的val。而不是这样写:

  baz = baz.toBuilder().setBar(
      baz.getBar().toBuilder().setFoo(
          baz.getBar().getFoo().toBuilder().setVal(10).build()
      ).build()).build();

你可以这样写:

  Baz.Builder builder = baz.toBuilder();
  builder.getBarBuilder().getFooBuilder().setVal(10);
  baz = builder.build();

嵌套类型

一个消息可以在另一个消息中声明。例如:message Foo {message Bar {}}。在这种情况下,编译器将生成Bar作为嵌套在Foo中的内部类。

字段

除了上一节中描述的方法外,协议缓冲区编译器为.proto文件中的消息中定义的每个字段生成一组访问方法。读取字段值的方法在消息类及其对应的构建器中定义;但修改值的方法仅在构建器中定义。

请注意,方法名称始终使用骆驼命名,即使.proto文件中的字段名称使用小写下划线形式(应该这样)。它也将做如下转换工作:

  1. 对于名称中的每个下划线,下划线将被删除,并且之后的首字母大写。
  2. 如果名称附有前缀(例如“get”),则第一个字母大写。否则,它是小写。

因此,foo_bar_baz字段变成fooBarBaz。如果前缀为get,那将是getFooBarBaz

除了访问器方法之外,编译器还会为包含其字段编号的每个字段生成一个整数常量。常量是将字段名称转换为大写,后跟_FIELD_NUMBER
例如,给定字段optional int32 foo_bar = 5;编译器将生成常量public static final int FOO_BAR_FIELD_NUMBER = 5;

单个字段(proto2)

对于任何这些字段定义:

optional int32 foo = 1;
required int32 foo = 1;

编译器将在消息类及其构建器中生成以下访问器方法:

  • boolean hasFoo(): 如果设置了该字段,则返回true。
  • int getFoo(): 返回字段的当前值。如果未设置该字段,则返回默认值。

编译器将仅在消息的构建器中生成以下方法:

  • Builder setFoo(int value): 设置字段的值。调用调用这个之后, hasFoo()将返回true,getFoo()将返回值。
  • Builder clearFoo(): 清除该字段的值。调用这个之后,hasFoo()将返回false,getFoo()将返回默认值。

对于其他简单的字段类型,根据标量值类型表选择相应的Java类型。对于消息和枚举类型,值类型将替换为消息或枚举类。

嵌入式消息字段

对于消息类型,setFoo()还接受消息的构建器类型的实例作为参数。这只是一个快捷方式,相当于在构建器上调用.build()并将结果传递给该方法。

如果该字段未设置,getFoo()将返回一个Foo实例,其中没有一个字段设置(可能是Foo.getDefaultInstance()返回的实例)。

此外,编译器生成两个访问器方法,允许您访问消息类型的相关子构建器。消息类及其构建器中会生成以下方法:

  • FooOrBuilder getFooOrBuilder(): 返回字段的构建器(如果已存在),或者返回消息(如果不存在)。

编译器仅在消息的构建器中生成以下方法:

  • Builder getFooBuilder(): 返回字段的构建器。

单个字段(proto3)

对于此字段定义:

int32 foo = 1;

编译器将在消息类及其构建器中生成以下访问器方法:

  • int getFoo(): 返回字段的当前值。如果未设置该字段,则返回字段类型的默认值。

编译器将仅在消息的构建器中生成以下方法:

  • Builder setFoo(int value): 设置字段的值。调用它之后,getFoo()将返回value
  • Builder clearFoo(): 清除该字段的值。调用它之后,getFoo()将返回字段类型的默认值。

对于其他简单的字段类型,根据标量值类型表选择相应的Java类型。对于消息和枚举类型,值类型将替换为消息或枚举类。

嵌入式消息字段

对于消息字段类型,在消息类及其构建器中生成一个附加的访问器方法:

  • boolean hasFoo(): 如果字段已设置,则返回true。

setFoo()还接受消息的构建器类型的实例作为参数。这只是一个快捷方式,相当于在构建器上调用.build()并将结果传递给该方法。

如果该字段未设置,getFoo()将返回一个Foo实例,其中没有一个字段设置(可能是Foo.getDefaultInstance()返回的实例)。

此外,编译器生成两个访问器方法,允许您访问消息类型的相关子构建器。消息类及其构建器中会生成以下方法:

  • FooOrBuilder getFooOrBuilder(): 返回字段的构建器(如果已存在),或者返回消息(如果不存在)。

编译器仅在消息的构建器中生成以下方法:

  • Builder getFooBuilder(): 返回字段的构建器。

枚举字段

对于枚举字段类型,在消息类及其构建器中生成一个附加的访问器方法:

  • int getFooValue(): 返回枚举的整型值。

编译器将仅在消息的构建器中生成以下附加方法:

  • Builder setFooValue(int value): 设置枚举的整型值。

此外,如果枚举值未知,getFoo()将返回UNRECOGNIZED - 这是proto3编译器向生成的枚举类型添加的特殊附加值。

重复字段

对于此字段定义:

repeated int32 foo = 1;

编译器将在消息类及其构建器中生成以下访问器方法:

  • int getFooCount(): 返回字段中当前的元素数。
  • int getFoo(int index): 返回给定基于零的索引的元素。
  • List<Integer> getFooList(): 将整个字段作为列表返回。如果未设置该字段,则返回一个空列表。返回的列表对于消息类是不可变的,对于构建器类是不可修改的。

编译器将仅在消息的构建器中生成以下方法:

  • Builder setFoo(int index, int value): 在给定的基于零的索引处设置元素的值。
  • Builder addFoo(int value): 用给定值添加一个新元素到该字段。
  • Builder addAllFoo(Iterable<? extends Integer> value):将给定Iterable中的所有元素添加到该字段。
  • Builder clearFoo(): 从字段中删除所有元素。调用这个之后,getFooCount()将返回零。

重复嵌入的消息字段

对于消息类型,setFoo()addFoo()也接受消息的构建器类型的实例作为参数。这只是一个快捷方式,相当于在构建器上调用.build()并将结果传递给该方法。

此外,编译器在消息类及其构建器中为消息类型生成以下附加访问器方法,允许您访问相关的子构建器:

  • FooOrBuilder getFooOrBuilder(int index): 返回指定元素的构建器(如果已经存在)或元素(如果不存在)。如果这是从消息类调用的,它将始终返回消息,而不是生成器。
  • List<FooOrBuilder> getFooOrBuilderList(): 将整个字段作为不可修改的构建器列表(如果可用)或消息(如果不存在)返回。如果这是从消息类调用的,它将始终返回不可变的消息列表,而不是不可修改的构建器列表。

编译器将仅在消息的构建器中生成以下方法:

  • Builder getFooBuilder(int index): 返回指定索引处元素的构建器。
  • Builder addFooBuilder(int index): 追加并返回指定索引处的默认消息实例的构建器。
  • Builder addFooBuilder(): 追加并返回默认消息实例的构建器。
  • Builder removeFoo(int index): 在给定的基于零的索引上移除元素。
  • List<Builder> getFooBuilderList(): 将整个字段作为不可修改的构建器列表返回。

重复枚举字段(仅限proto3)

编译器将在消息类及其构建器中生成以下附加方法:

  • int getFooValue(int index): 返回指定索引处枚举的整型值。
  • List getFooValueList(): 返回整个字段的整型列表。

编译器将仅在消息的构建器中生成以下附加方法:

  • Builder setFooValue(int index, int value): 设置指定索引处枚举的整数值。

Oneof字段

对于这个字段定义:

oneof oneof_name {
    int32 foo = 1;
    ...
}

编译器将在消息类及其构建器中生成以下访问器方法:

  • boolean hasFoo()(仅限proto2): 如果oneof案例是FOO,则返回true。
  • int getFoo(): 如果oneof案例是FOO,则返回oneof_name的当前值。否则返回该字段的默认值。

编译器将仅在消息的构建器中生成以下方法:

  • Builder setFoo(int value): 将oneof_name设置为此value并将oneof案例设置为FOO。调用这个之后,hasFoo()将返回true,getFoo()将返回值,getOneofNameCase()将返回FOO

  • Builder clearFoo():

    • 如果oneof案例不是FOO,没有任何改变。
    • 如果oneof案例是FOO,将oneof_name设置为null,将oneof案例设置为ONEOFNAME_NOT_SET。调用这个之后,hasFoo()将返回false,getFoo()将返回默认值,getOneofNameCase()将返回ONEOFNAME_NOT_SET

Oneof字段想法上感觉有点像C语言中的共用体(Union)。

对于其他简单的字段类型,根据标量值类型表选择相应的Java类型。对于消息和枚举类型,值类型将替换为消息或枚举类。

Map字段

对于map字段定义:

map<int32, int32> weight = 1;

编译器将在消息类及其构建器中生成以下访问器方法:

  • Map<Integer, Integer> getWeightMap();: 返回一个不可修改的Map
  • int getWeightOrDefault(int key, int default);: 返回键对应的值,如果不存在,则返回默认值。
  • int getWeightOrThrow(int key);: 返回键对应的值,如果不存在,则抛出IllegalArgumentException
  • boolean containsWeight(int key);: 该字段中是否存在该键。
  • int getWeightCount();: 返回Map中元素的数量。

编译器将仅在消息的构建器中生成以下方法:

  • Builder putWeight(int key, int value);: 添加一个新的K-V映射关系。
  • Builder putAllWeight(Map<Integer, Integer> value);: 添加Map中所有的实体到该字段。
  • Builder removeWeight(int key);: 移除某个key对应的映射关系。
  • Builder clearWeight();: 移除所有的映射关系。
  • @Deprecated Map<Integer, Integer> getMutableWeight();: 返回可变Map。请注意,对此方法的多次调用可能会返回不同的Map实例。返回的Map引用可能会对后续的方法调用构建器无效。

Any

给定一个Any这样的字段:

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  google.protobuf.Any details = 2;
}

在我们生成的代码中,details字段的getter返回一个com.google.protobuf.Any的实例。这提供了以下特殊方法来打包和解压缩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;
}

Oneofs

给出一个Oneofs的定义, 如:

oneof oneof_name {
    int32 foo_int = 4;
    string foo_string = 9;
    ...
}

oneof_nameoneof中的所有字段将使用共享的oneof_name对象作为其值。另外,协议缓冲区编译器将为一个case生成一个Java枚举类型,如下所示:

public enum OneofNameCase
        implements com.google.protobuf.Internal.EnumLite {
      FOO_INT(4),
      FOO_STRING(9),
      ...
      ONEOFNAME_NOT_SET(0);
      ...
    };

此枚举类型的值具有以下特殊方法:

  • int getNumber(): 返回.proto文件中定义的对象的数值。
  • static OneofNameCase forNumber(int value): 返回与给定数值对应的枚举对象(或其他数值则返回null)。

枚举

给定一个枚举定义,如:

enum Foo {
  VALUE_A = 0;
  VALUE_B = 5;
  VALUE_C = 1234;
}

协议缓冲区编译器将生成一个名为Foo的Java枚举类型,具有相同的值集。如果您使用proto3,它还将特殊值UNRECOGNIZED添加到枚举类型。生成的枚举类型的值具有以下特殊方法:

  • int getNumber(): 返回.proto文件中定义的对象的数值。
  • EnumValueDescriptorgetValueDescriptor(): 返回值的描述符,其中包含有关该值的名称,数值和类型的信息。
  • EnumDescriptorgetDescriptorForType(https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/Descriptors.EnumDescriptor): 返回枚举类型的描述符,其中包含关于每个定义值的信息。

另外,Foo枚举类型包含以下静态方法:

  • static Foo forNumber(int value): 返回与给定数值对应的枚举对象。当没有相应的枚举对象时返回null。
  • static Foo valueOf(int value): 返回与给定数值对应的枚举对象。该方法已被弃用,可使用forNumber(int value)替代,并将在即将发布的版本中删除。
  • static Foo valueOf(EnumValueDescriptor descriptor): 返回与给定值描述符相对应的枚举对象。可能比valueOf(int)快。在proto3中,如果传递了未知值描述符,则返回UNRECOGNIZED
  • EnumDescriptor getDescriptor(): 返回枚举类型的描述符,其中包含关于每个定义值的信息。(这不同于getDescriptorForType()只是因为它是一个静态方法。)

也会为每个枚举值生成后缀是_VALUE的整数常量。

请注意,.proto语言允许多个枚举符号具有相同的数值。具有相同数值的符号是同义词。例如:

enum Foo {
  BAR = 0;
  BAZ = 0;
}

在这种情况下,BAZBAR的同义词。在Java中,BAZ将被定义为一个static final字段,如下所示:

static final Foo BAZ = BAR;

因此,BAR和BAZ比较相等,BAZ不应该出现在switch语句中。编译器始终选择用给定数值定义的第一个符号作为该符号的“规范”版本;具有相同数字的所有后续符号只是别名。

枚举可以定义嵌套在消息类型中。编译器会生成嵌套在该消息类型的类中的Java枚举定义。

扩展(仅限于proto2)

给定一个扩展范围的消息:

message Foo {
  extensions 100 to 199;
}

协议缓冲区编译器将使Foo继承GeneratedMessage.ExtendableMessage而不是通常的GeneratedMessage。类似地,Foo的构建器将扩展GeneratedMessage.ExtendableBuilder
你不应该通过名称引用这些基类型(GeneratedMessage被认为是一个实现细节)。但是,这些超类定义了一些可用于操作extensions的附加方法。

特别是FooFoo.Builder将继承方法hasExtension()getExtension()getExtensionCount()。此外,Foo.Builder将继承方法setExtension()clearExtension()
这些方法中的每一个都采用标识扩展字段的扩展标识符作为其第一个参数(如下所述);剩余的参数和返回值与对于与扩展标识符相同类型的普通(非扩展)字段生成的相应访问器方法的参数和返回值完全相同。

给定一个扩展定义:

extend Foo {
  optional int32 bar = 123;
}

协议缓冲区编译器生成一个名为bar的“扩展标识符”,您可以使用Foo的扩展访问器访问此扩展名,如下所示:

Foo foo =
  Foo.newBuilder()
     .setExtension(bar, 1)
     .build();
assert foo.hasExtension(bar);
assert foo.getExtension(bar) == 1;

(扩展标识符的确切实现是复杂的,涉及使用泛型 - 但是,您不需要担心扩展标识符如何使用它们。)

注意,bar将被声明为.proto文件的外部类的静态字段,如上所述。我们在示例中省略了外部类名称。

扩展可以被声明为嵌套在另一个类型之内。例如,做这样的事情的常见模式:

message Baz {
  extend Foo {
    optional Baz foo_ext = 124;
  }
}

在这种情况下,扩展名标识符foo_ext被声明嵌套在Baz内。它可以这样使用:

Baz baz = createMyBaz();
Foo foo =
  Foo.newBuilder()
     .setExtension(Baz.fooExt, baz)
     .build();

解析可能有扩展名的消息时,您必须提供一个ExtensionRegistry,其中您已经注册了任何您想要解析的扩展名。否则,这些扩展名将被视为未知字段。例如:

 ExtensionRegistry registry = ExtensionRegistry.newInstance();
 registry.add(Baz.fooExt);
 Foo foo = Foo.parseFrom(input, registry);

服务

如果.proto文件包含如下行:

option java_generic_services = true;

那么,protocol buffer编译器会根据在.proto中定义的服务生成代码。然而,生成的代码可能会不好用,因为它它未绑定到任何特定的RCP系统,这样的话,绑定到一个RPC系统会需要更多层次的间接的代码调整。如果你不想生成代码,把这行添加到.proto文件中:

option java_generic_services = false;

如果上边的语句未给出,这个选项默认是false,因为通用服务已经过时。(注: 在2.4.0版本之前, 这个选项默认是true)

基于.proto-语言服务定义的RPC系统,应该提供一个插件用来生成适用于该系统的代码。这些插件可能要求抽象服务被禁用,以便他们可以生成自己同名的类。插件在2.3.0 (January 2010)版本中是新的。

剩下的章节描述了当抽象服务是enable的情况下protocol buffer编译器会生成什么东西。

接口

给一个服务定义:

service Foo {
  rpc Bar(FooRequest) returns(FooResponse);
}

protocol buffer编译器会生成抽象类Foo来表示这个服务。对于定义在服务中的每个方法,Foo类都会有一个抽象方法与之对应。这种情况下,Bar方法是这样定义的:

abstract void bar(RpcController controller, FooRequest request,
                  RpcCallback<FooResponse> done);

参数和Service.CallMethod()的参数是等同的,除了方法参数是隐式,requestdone都明确地指定了类型。

Foo实现了Service 接口。Protocol buffer 编译器自动生成如下接口方法的实现:

  • getDescriptorForType: 返回服务的ServiceDescriptor
  • callMethod: 基于提供的方法描述符决定哪个方法被调用,然后直接调用该方法,向下转型请求消息和回调为正确的类型。
  • getRequestPrototypegetRequestPrototype: 为给定的方法返回正确类型的请求或者响应的默认实例。

如下的静态方法也被生成:

static ServiceDescriptor getDescriptor(): 返回这个类型的描述符,它包含了关于这个服务中有哪些方法以及方法的输入和输出类型等信息。

Foo还包含了一个内嵌的接口Foo.Interface。这个是一个纯接口,再一次包含了定义于你的服务中的每个方法。然而,这个接口不继承自Service接口。这个问题是由于RPC服务器实现,通常会用抽象服务的对象写,而不是你特定服务。
为了解决这个问题,如果你有一个object impl实现了Foo.Interface,你可以调用Foo.newReflectiveService(impl)来构建一个作为impl的简单代理的Foo类的实例,并且实现服务。

总括来说, 当实现你自己的服务的时候,你有两个选项:

  • Foo子类并根据需要实现其方法,然后将子类的实例直接传递给RPC服务器实现。这通常很简单,但是有些人认为它不够“纯净”。
  • 实现Foo.Interface并使用Foo.newReflectiveService(Foo.Interface)构造一个包装它的服务,然后将包装器传递给您的RPC实现。

存根

Protocol buffer编译器还生成每个服务接口的“stub”实现,这是由希望向执行服务的服务器发送请求的客户端使用的。对于Foo服务(上述),存根实现Foo.Stub将被定义为一个内部类。

  • Foo.Stub(RpcChannel channel): 构造一个通过给定的通道发送请求的新的stub。
  • RpcChannelgetChannel(): 返回传递给构造函数的当前stub的通道。

Stub包装了通道,实现了每个service定义的方法,对每个方法的调用,最终都是对channel.callMethod()的简单调用。

Protocol buffer类库不包括RPC实现。但是,它包含了所有你需要的工具,连接一个生成的服务类到任意RPC实现。您只需要提供RpcChannelRpcController的实现。

阻塞接口

上述的RPC类都有非阻塞(non-blocking)语义:当你调用一个方法的时候,提供一个方法完成时调用的回调对象。通常编写阻塞(blocking)语义的代码更容易一些(即便扩展性差一些),方法直到完成时才会返回。
为了满足上述要求,Protocol buffer编译器也生成了服务的阻塞版本。Foo.BlockingInterface等同于Foo.Interface,除了每个方法不是使用回调而只是简单地返回结果。那么,举个例子,bar被定义为:

abstract FooResponse bar(RpcController controller, FooRequest request)
                         throws ServiceException;

和非阻塞服务相似,Foo.newReflectiveBlockingService(Foo.BlockingInterface)返回一个阻塞的服务,它包装了Foo.BlockingInterface。最终,Foo.BlockingStub返回一个Foo.BlockingInterfacestub实现,这个接口会发送请求到特定的BlockingRpcChannel(阻塞通道)。

插件插入点

代码生成插件想要扩展Java代码生成器的输出,可能会使用给定的插入点的名字插入如下类型的代码:

  • outer_class_scope:属于外部类成员声明。
  • class_scope:TYPENAME: 属于消息类的成员声明。TYPENAME是完整的proto名字,比如包名.消息类型——package.MessageType
  • builder_scope:TYPENAME:属于消息构造器类的成员声明。TYPENAME是完整的proto名字,比如包名.消息类型——package.MessageType
  • enum_scope:TYPENAME: 属于枚举类的成员声明。TYPENAME是完整的proto枚举名字, 比如包名.枚举类型——package.EnumType

生成的代码不能包含import语句,因为它们有可能和定义在生成代码中的类型名字冲突。当指向一个外部类的时候,你必须总是是用全路径名称。

注意:对于Java代码生成器,决定输出文件名称的逻辑非常复杂。你可能需要查看protoc的源码,特别是java_headers.cc,确保你考虑到了所有的情形。

注意:不要依赖标准代码生成器声明的私有类属性来生成代码,因为这些实现细节可能在未来的Protocol Buffers的版本中会有改动。

参考链接: Java Generated Code & org.xolstice.maven.plugins/protobuf-maven-plugin

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值