本页面描述了协议缓冲区编译器为任何给定协议定义生成的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/com
和build/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
文件中的字段名称使用小写下划线形式(应该这样)。它也将做如下转换工作:
- 对于名称中的每个下划线,下划线将被删除,并且之后的首字母大写。
- 如果名称附有前缀(例如“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案例不是
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_name
oneof中的所有字段将使用共享的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
文件中定义的对象的数值。- EnumValueDescriptor
getValueDescriptor()
: 返回值的描述符,其中包含有关该值的名称,数值和类型的信息。 - EnumDescriptor
getDescriptorForType(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;
}
在这种情况下,BAZ
是BAR
的同义词。在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
的附加方法。
特别是Foo
和Foo.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()
的参数是等同的,除了方法参数是隐式,request
和done
都明确地指定了类型。
Foo实现了Service 接口。Protocol buffer 编译器自动生成如下接口方法的实现:
getDescriptorForType
: 返回服务的ServiceDescriptor
。callMethod
: 基于提供的方法描述符决定哪个方法被调用,然后直接调用该方法,向下转型请求消息和回调为正确的类型。getRequestPrototype
和getRequestPrototype
: 为给定的方法返回正确类型的请求或者响应的默认实例。
如下的静态方法也被生成:
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。- RpcChannel
getChannel()
: 返回传递给构造函数的当前stub的通道。
Stub
包装了通道,实现了每个service
定义的方法,对每个方法的调用,最终都是对channel.
callMethod()的简单调用。
Protocol buffer
类库不包括RPC实现。但是,它包含了所有你需要的工具,连接一个生成的服务类到任意RPC实现。您只需要提供RpcChannel和RpcController的实现。
阻塞接口
上述的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.BlockingInterface
的stub
实现,这个接口会发送请求到特定的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