对非optimize_for为lite_runtime的proto文件,protobuf编译器会在编译出的java代码文件末尾添加一个filedescriptor静态字段以描述该proto文件定义时的所有元数据信息、为每个message对象定义一个descriptor静态字段以描述该message定义时的元数据信息、为每个message对象定义一个fieldaccessortable静态字段用于使用反射读取/设置某个字段的值等(以提供generatedmessage中方法的反射实现):
private static descriptor inter-nal_static_levin_protobuf_result_descriptor;
private static fieldaccessortable inter-nal_static_levin_protobuf_result_fieldaccessortable;
private static descriptor inter-nal_static_levin_protobuf_searchresponse_descriptor;
private static fieldaccessortable inter-nal_static_levin_protobuf_searchresponse_fieldaccessortable;
private static filedescriptor descriptor;
在protobuf中存在多种类型的元数据描述类:
1. filedescriptor:对一个proto文件的描述,它包含文件名、包名、选项(如java_package、java_outer_classname等)、文件中定义的所有message、文件中定义的所有enum、文件中定义的所有service、文件中所有定义的extension、文件中定义的所有依赖文件(import)等。在filedescriptor中还存在一个descriptorpool实例,它保存了所有的dependencies(依赖文件的filedescriptor)、name到genericdescriptor的映射、字段到fielddescriptor的映射、枚举项到enumvaluedescriptor的映射,从而可以从该descriptorpool中查找相关的信息,因而可以通过名字从filedescriptor中查找message、enum、service、extensions等。
2. descriptor:对一个message定义的描述,它包含该message定义的名字、所有字段、内嵌message、内嵌enum、关联的filedescriptor等。可以使用字段名或字段号查找fielddescriptor。
3. fielddescriptor:对一个字段或扩展字段定义的描述,它包含字段名、字段号、字段类型、字段定义(required/optional/repeated/packed)、默认值、是否是扩展字段以及和它关联的descriptor/filedescriptor等。
4. enumdescriptor:对一个enum定义的描述,它包含enum名、全名、和它关联的filedescriptor。可以使用枚举项或枚举值查找enumvaluedescriptor。
5. enumvaluedescriptor:对一个枚举项定义的描述,它包含枚举名、枚举值、关联的enumdescriptor/filedescriptor等。
6. servicedescriptor:对一个service定义的描述,它包含service名、全名、关联的filedescriptor等。
7. methoddescriptor:对一个在service中的method的描述,它包含method名、全名、参数类型、返回类型、关联的filedescriptor/servicedescriptor等。
最后,protobuf编译生成的代码末尾还有一个descriptordata字符串数组,它是序列化后的filedescriptorproto数据,在静态初始化块中可以调用filedescriptor.internalbuildgeneratedfilefrom()方法构造整个filedescriptor实例,在完成filedescriptor的构造后,还会回调传入的internaldescriptorassigner实例以初始化其他的静态字段,如以上提到的所有的静态字段。
在protobuf中descriptor的类图:
序列化和反序列化是protobuf最基础的框架,它使用messagelite/message接口来抽象一个可序列化的实例,并且使用builder从字节数组或输入字节流中构建messagelite/message实例,messagelite和message内部都定义了自己的builder类,他们个字继承自messageliteorbuilder以及messageorbuiler,它们定义了messagelite/message和它们各自builder类的共同接口。
messageliteorbuilder接口只定义了messagelite和messagelite.builder两个接口共有的两个方法:getdefaultinstancefortype()方法获取一个当前还未初始化的当前message实例(没有字段被赋值,因而所有字段返回默认值,对repeat字段返回空,在当前protobuf 2.5.0的实现中,它返回的是一个单例,和每个生成的静态方法getdefaultinstance()返回相同的实例);isinitialized()方法用来判断是否所有required字段已经被赋值。messagelite接口中定义了两个writeto()方法分别将当前实例序列化并写入输出字节流中,而另一个writedelimitedto()方法则在写入之前将当前实例的总长度写入输出字节流中(以可变长32位int编码方式),从而可以同时向一个输出字节流中写入多个message实例;messagelite中还定义了获取当前messagelite在序列化成字节流后的总字节数的方法getserializedsize(),两个直接返回字节数组的tobytearray()/tobytestring()方法,以及获取它的parser实例(getparserfortype())和返回它的builder实例(tobuilder()-创建一个新的builder实例/newbuilderfortype()-用当前messagelite类初始化一个新的builder实例并返回)方法。其中builder接口用于从字节流或字节数组中解析并构造messagelite对象(各种版本的mergefrom()方法,如果发送端写入了messagelite字节长度,则使用mergedelimitedfrom()方法),最后builder使用build()方法构造messagelite对象,此时如果有required字段还未被设置,会抛出uninitializedmessageexception,为了避免抛出异常,可以使用buildpartial()方法;另外builder还定义了clone()和clear()方法;在生成的每个message对象中都定义了一个newbuilder()静态方法,一般使用该静态方法初始化一个builder实例。parser接口也定义了各个版本的parsefrom()/parsepartialfrom()/parsedelimitedfrom()/parsepartialdelimitedfrom()方法用来从字节数组或字节流中解析出message实例,在生成的代码中,builder的实现直接调用parser实现类中的方法。
在大部分情况下,messagelite已经能完成所有的序列化和反序列化操作了,特别是一些资源有限额手持设备,它如果运行整个protobuf库会显得太耗资源;可以在.proto文件中加入一下指令来告诉protobuf编译器只需要生成实现messagelite的类:
option optimize_for = lite_runtime
然而对一般的server程序来说,我们并不在乎这点资源的损耗,因而会选择实现message接口,它相比messagelite,添加了descriptors相关的支持,即支持使用fielddescriptor来构建message.builder实例并最终构建message实例。
messageorbuilder接口继承自messageliteorbuilder接口,它定义了message和message.builder共有的接口,即添加了descriptor、fielddescriptor等相关的扩展。由于实现message和message.builder接口的类保存了所有message定义时具有的信息(文件名、包名、字段列表等,使用各种descriptor类来抽象),因而我们可以使用message/message.builder类获取到更多的信息,如一个message/message.builder没有赋值所有required的字段,可以使用findinitializationerrors()方法来获取所有未赋值的字段列表(字段的全路径名,getinitializationerrorstring()是这个列表的字符串形式表达,为了提升性能,建议使用isinitialized()方法先做初步判断,因为它更快);另外在messageorbuilder中还定义了当前message对应的descriptor实例:getdescriptorfortype()方法,获取所有已经赋值的fielddescriptor到其值的一个map:getallfields(),通过fielddescriptor取得其值:getfield(),判断一个字段是否已经被赋值:hasfield(),获取repeated字段的count:getrepeatedfieldcount(),通过fielddescriptor以及index获取repeated字段在index处的值:getrepeatedfield(),获取未知的字段:getunknownfields()。message接口除了继承自messageorbuilder接口的方法,并没有定义多余的方法,只是添加了equals、hashcode、tostring方法的定义。而message.builder接口除了继承自messageorbuilder接口以外,它还定义了基于fielddescriptor的方法,如通过fielddescriptor创建/获取builder实例:newbuilderforfileld()/getfieldbuilder(),通过fielddescriptor设置/清除字段的值:setfield()/clearfield()/setrepeatedfield()/addrepeatedfield(),以及设置unknownfields:setunknownfields()/mergeunknownfields()。
messagelite/message类图如下:
除了序列化框架,protobuf还定义了一套简单的rpc框架。之所以说简单是因为它定义的service层接口的协议,而没有具体和传输相关的实现,而只是将传输相关的逻辑抽象成rpcchannel和blockingrpcchannel分别用于表示同步和一步方式的service方法调用,而至于底层用什么样的协议和框架,由用户自己决定并实现。
所谓rpc框架,从用户角度上最基本的就是定义客户端和服务器端的协议,即服务器端暴露出什么样的接口供客户端调用,这个接口定义了服务器在一个host的某个(些)端口上接收某些请求数据,并期望能返回的响应。其中服务器和端口号属于传输实现的范畴,protobuf只是用rpcchannel/blockingrpcchannel的概念做了抽象,而没有给出具体实现;而接收某个请求数据以及期待的响应数据,在protobuf使用service/blockingservice抽象来定义,并且这也是protobuf中rpc框架的定义部分,其中service和rpcchannel共同构成异步方式的rpc框架,而blockingservice和blockingrpcchannel共同构成了同步(阻塞)方式的rpc框架。
从底层实现的角度,一个rpc调用就是客户端发送一些请求数据给服务器,服务器解析并处理这些请求数据,然后将响应数据返回给客户端。为了隐藏内部实现细节,提升写代码的效率,rpc将这一过程封装成方法调用,即不同的请求用不同的方法表达,这就是protobuf中rpc的定义。在protobuf中,定义一个prc接口比较简单:首先开启rpc功能,然后用service关键字定义一个接口,在接口中使用rpc关键字定义一个方法,方法包含方法名、方法参数、返回值,其中方法参数和返回值都必须是一个message类型,并且只能有一个:
option java_generic_services = true;
service myservice {
rpc request(searchrequest) returns(searchresponse);
}
在protobuf编译生成的代码中,它会生成一个myservice抽象类实现了service接口,一般它只是作为一个命名空间,它内部定义了两个接口:interface和blockinginterface本别继承自service接口和blockingservice接口,用于抽象异步和同步方式的rpc方法调用;这两个接口有两个实现类:stub和blockingstub,他们分别接收rpcchannel和blockingrpcchannel实例作为构造函数参数,可以使用myservice中的静态方法newstub()和newblockingstub()方法获取他们各自实例,他们主要用于客户端的调用。在生成的request方法中,除了request本身的参数,还有一个rpccontroller参数,它用于处理在rpcchannel/blockingrpcchannel调用中的状态处理,如错误处理等,使用它可以获知此次调用是否出错,错误信息是什么等。在myservice中还定义了两个静态方法newreflectiveservice/newreflectiveblockingservice,他们接收interface/blockinginterface实例,并返回service/blockingservice的实现实例(暂时还没有想到使用他们的场景)。
在myservice的rpc框架实现中,在服务器端,实现myservice.interface/myservice.blockinginterface接口,然后将它注册到对rpcchannel/blockingrpcchannel框架的实现中;在客户端则创建一个rpcchannel/blockingrpcchannel实例,传入myservice.newstub()/myservice.newblockingstub()方法获取对应的实例,然后使用这个stub/blockingstub实例调用相应的方法即可。