Descriptor框架
對非optimize_for為LITE_RUNTIME的proto文件,protobuf編譯器會在編譯出的Java代碼文件末尾添加一個(gè)FileDescriptor靜態(tài)字段以描述該proto文件定義時(shí)的所有元數(shù)據(jù)信息、為每個(gè)message對象定義一個(gè)Descriptor靜態(tài)字段以描述該message定義時(shí)的元數(shù)據(jù)信息、為每個(gè)message對象定義一個(gè)FieldAccessorTable靜態(tài)字段用于使用反射讀取/設(shè)置某個(gè)字段的值等(以提供GeneratedMessage中方法的反射實(shí)現(xiàn)): 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中存在多種類型的元數(shù)據(jù)描述類:
1. FileDescriptor:對一個(gè)proto文件的描述,它包含文件名、包名、選項(xiàng)(如java_package、java_outer_classname等)、文件中定義的所有message、文件中定義的所有enum、文件中定義的所有service、文件中所有定義的extension、文件中定義的所有依賴文件(import)等。在FileDescriptor中還存在一個(gè)DescriptorPool實(shí)例,它保存了所有的dependencies(依賴文件的FileDescriptor)、name到GenericDescriptor的映射、字段到FieldDescriptor的映射、枚舉項(xiàng)到EnumValueDescriptor的映射,從而可以從該DescriptorPool中查找相關(guān)的信息,因而可以通過名字從FileDescriptor中查找Message、Enum、Service、Extensions等。
2. Descriptor:對一個(gè)message定義的描述,它包含該message定義的名字、所有字段、內(nèi)嵌message、內(nèi)嵌enum、關(guān)聯(lián)的FileDescriptor等。可以使用字段名或字段號查找FieldDescriptor。
3. FieldDescriptor:對一個(gè)字段或擴(kuò)展字段定義的描述,它包含字段名、字段號、字段類型、字段定義(required/optional/repeated/packed)、默認(rèn)值、是否是擴(kuò)展字段以及和它關(guān)聯(lián)的Descriptor/FileDescriptor等。
4. EnumDescriptor:對一個(gè)enum定義的描述,它包含enum名、全名、和它關(guān)聯(lián)的FileDescriptor。可以使用枚舉項(xiàng)或枚舉值查找EnumValueDescriptor。
5. EnumValueDescriptor:對一個(gè)枚舉項(xiàng)定義的描述,它包含枚舉名、枚舉值、關(guān)聯(lián)的EnumDescriptor/FileDescriptor等。
6. ServiceDescriptor:對一個(gè)service定義的描述,它包含service名、全名、關(guān)聯(lián)的FileDescriptor等。
7. MethodDescriptor:對一個(gè)在service中的method的描述,它包含method名、全名、參數(shù)類型、返回類型、關(guān)聯(lián)的FileDescriptor/ServiceDescriptor等。
最后,protobuf編譯生成的代碼末尾還有一個(gè)descriptorData字符串?dāng)?shù)組,它是序列化后的FileDescriptorProto數(shù)據(jù),在靜態(tài)初始化塊中可以調(diào)用FileDescriptor.internalBuildGeneratedFileFrom()方法構(gòu)造整個(gè)FileDescriptor實(shí)例,在完成FileDescriptor的構(gòu)造后,還會回調(diào)傳入的InternalDescriptorAssigner實(shí)例以初始化其他的靜態(tài)字段,如以上提到的所有的靜態(tài)字段。
在protobuf中Descriptor的類圖:

Message、MessageLite框架
序列化和反序列化是protobuf最基礎(chǔ)的框架,它使用MessageLite/Message接口來抽象一個(gè)可序列化的實(shí)例,并且使用Builder從字節(jié)數(shù)組或輸入字節(jié)流中構(gòu)建MessageLite/Message實(shí)例,MessageLite和Message內(nèi)部都定義了自己的Builder類,他們個(gè)字繼承自MessageLiteOrBuilder以及MessageOrBuiler,它們定義了MessageLite/Message和它們各自Builder類的共同接口。
MessageLiteOrBuilder接口只定義了MessageLite和MessageLite.Builder兩個(gè)接口共有的兩個(gè)方法:getDefaultInstanceForType()方法獲取一個(gè)當(dāng)前還未初始化的當(dāng)前Message實(shí)例(沒有字段被賦值,因而所有字段返回默認(rèn)值,對repeat字段返回空,在當(dāng)前protobuf 2.5.0的實(shí)現(xiàn)中,它返回的是一個(gè)單例,和每個(gè)生成的靜態(tài)方法getDefaultInstance()返回相同的實(shí)例);isInitialized()方法用來判斷是否所有required字段已經(jīng)被賦值。MessageLite接口中定義了兩個(gè)writeTo()方法分別將當(dāng)前實(shí)例序列化并寫入輸出字節(jié)流中,而另一個(gè)writeDelimitedTo()方法則在寫入之前將當(dāng)前實(shí)例的總長度寫入輸出字節(jié)流中(以可變長32位Int編碼方式),從而可以同時(shí)向一個(gè)輸出字節(jié)流中寫入多個(gè)Message實(shí)例;MessageLite中還定義了獲取當(dāng)前MessageLite在序列化成字節(jié)流后的總字節(jié)數(shù)的方法getSerializedSize(),兩個(gè)直接返回字節(jié)數(shù)組的toByteArray()/toByteString()方法,以及獲取它的Parser實(shí)例(getParserForType())和返回它的Builder實(shí)例(toBuilder()-創(chuàng)建一個(gè)新的Builder實(shí)例/newBuilderForType()-用當(dāng)前MessageLite類初始化一個(gè)新的Builder實(shí)例并返回)方法。其中Builder接口用于從字節(jié)流或字節(jié)數(shù)組中解析并構(gòu)造MessageLite對象(各種版本的mergeFrom()方法,如果發(fā)送端寫入了MessageLite字節(jié)長度,則使用mergeDelimitedFrom()方法),最后Builder使用build()方法構(gòu)造MessageLite對象,此時(shí)如果有required字段還未被設(shè)置,會拋出UninitializedMessageException,為了避免拋出異常,可以使用buildPartial()方法;另外Builder還定義了clone()和clear()方法;在生成的每個(gè)Message對象中都定義了一個(gè)newBuilder()靜態(tài)方法,一般使用該靜態(tài)方法初始化一個(gè)Builder實(shí)例。Parser接口也定義了各個(gè)版本的parseFrom()/parsePartialFrom()/parseDelimitedFrom()/parsePartialDelimitedFrom()方法用來從字節(jié)數(shù)組或字節(jié)流中解析出Message實(shí)例,在生成的代碼中,Builder的實(shí)現(xiàn)直接調(diào)用Parser實(shí)現(xiàn)類中的方法。
在大部分情況下,MessageLite已經(jīng)能完成所有的序列化和反序列化操作了,特別是一些資源有限額手持設(shè)備,它如果運(yùn)行整個(gè)protobuf庫會顯得太耗資源;可以在.proto文件中加入一下指令來告訴protobuf編譯器只需要生成實(shí)現(xiàn)MessageLite的類:
option optimize_for = LITE_RUNTIME
然而對一般的Server程序來說,我們并不在乎這點(diǎn)資源的損耗,因而會選擇實(shí)現(xiàn)Message接口,它相比MessageLite,添加了Descriptors相關(guān)的支持,即支持使用FieldDescriptor來構(gòu)建Message.Builder實(shí)例并最終構(gòu)建Message實(shí)例。
MessageOrBuilder接口繼承自MessageLiteOrBuilder接口,它定義了Message和Message.Builder共有的接口,即添加了Descriptor、FieldDescriptor等相關(guān)的擴(kuò)展。由于實(shí)現(xiàn)Message和Message.Builder接口的類保存了所有Message定義時(shí)具有的信息(文件名、包名、字段列表等,使用各種Descriptor類來抽象),因而我們可以使用Message/Message.Builder類獲取到更多的信息,如一個(gè)Message/Message.Builder沒有賦值所有required的字段,可以使用findInitializationErrors()方法來獲取所有未賦值的字段列表(字段的全路徑名,getInitializationErrorString()是這個(gè)列表的字符串形式表達(dá),為了提升性能,建議使用isInitialized()方法先做初步判斷,因?yàn)樗?/span>);另外在MessageOrBuilder中還定義了當(dāng)前Message對應(yīng)的Descriptor實(shí)例:getDescriptorForType()方法,獲取所有已經(jīng)賦值的FieldDescriptor到其值的一個(gè)Map:getAllFields(),通過FieldDescriptor取得其值:getField(),判斷一個(gè)字段是否已經(jīng)被賦值:hasField(),獲取repeated字段的count:getRepeatedFieldCount(),通過FieldDescriptor以及index獲取repeated字段在index處的值:getRepeatedField(),獲取未知的字段:getUnknownFields()。Message接口除了繼承自MessageOrBuilder接口的方法,并沒有定義多余的方法,只是添加了equals、hashCode、toString方法的定義。而Message.Builder接口除了繼承自MessageOrBuilder接口以外,它還定義了基于FieldDescriptor的方法,如通過FieldDescriptor創(chuàng)建/獲取Builder實(shí)例:newBuilderForFileld()/getFieldBuilder(),通過FieldDescriptor設(shè)置/清除字段的值:setField()/clearField()/setRepeatedField()/addRepeatedField(),以及設(shè)置UnknownFields:setUnknownFields()/mergeUnknownFields()。
MessageLite/Message類圖如下:

RPC框架
除了序列化框架,protobuf還定義了一套簡單的RPC框架。之所以說簡單是因?yàn)樗x的Service層接口的協(xié)議,而沒有具體和傳輸相關(guān)的實(shí)現(xiàn),而只是將傳輸相關(guān)的邏輯抽象成RpcChannel和BlockingRpcChannel分別用于表示同步和一步方式的Service方法調(diào)用,而至于底層用什么樣的協(xié)議和框架,由用戶自己決定并實(shí)現(xiàn)。
所謂RPC框架,從用戶角度上最基本的就是定義客戶端和服務(wù)器端的協(xié)議,即服務(wù)器端暴露出什么樣的接口供客戶端調(diào)用,這個(gè)接口定義了服務(wù)器在一個(gè)Host的某個(gè)(些)端口上接收某些請求數(shù)據(jù),并期望能返回的響應(yīng)。其中服務(wù)器和端口號屬于傳輸實(shí)現(xiàn)的范疇,protobuf只是用RpcChannel/BlockingRpcChannel的概念做了抽象,而沒有給出具體實(shí)現(xiàn);而接收某個(gè)請求數(shù)據(jù)以及期待的響應(yīng)數(shù)據(jù),在protobuf使用Service/BlockingService抽象來定義,并且這也是protobuf中RPC框架的定義部分,其中Service和RpcChannel共同構(gòu)成異步方式的RPC框架,而BlockingService和BlockingRpcChannel共同構(gòu)成了同步(阻塞)方式的RPC框架。
從底層實(shí)現(xiàn)的角度,一個(gè)RPC調(diào)用就是客戶端發(fā)送一些請求數(shù)據(jù)給服務(wù)器,服務(wù)器解析并處理這些請求數(shù)據(jù),然后將響應(yīng)數(shù)據(jù)返回給客戶端。為了隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié),提升寫代碼的效率,RPC將這一過程封裝成方法調(diào)用,即不同的請求用不同的方法表達(dá),這就是protobuf中RPC的定義。在protobuf中,定義一個(gè)PRC接口比較簡單:首先開啟RPC功能,然后用service關(guān)鍵字定義一個(gè)接口,在接口中使用rpc關(guān)鍵字定義一個(gè)方法,方法包含方法名、方法參數(shù)、返回值,其中方法參數(shù)和返回值都必須是一個(gè)message類型,并且只能有一個(gè):
option java_generic_services = true;
service MyService {
rpc request(SearchRequest) returns(SearchResponse);
}
在protobuf編譯生成的代碼中,它會生成一個(gè)MyService抽象類實(shí)現(xiàn)了Service接口,一般它只是作為一個(gè)命名空間,它內(nèi)部定義了兩個(gè)接口:Interface和BlockingInterface本別繼承自Service接口和BlockingService接口,用于抽象異步和同步方式的RPC方法調(diào)用;這兩個(gè)接口有兩個(gè)實(shí)現(xiàn)類:Stub和BlockingStub,他們分別接收RpcChannel和BlockingRpcChannel實(shí)例作為構(gòu)造函數(shù)參數(shù),可以使用MyService中的靜態(tài)方法newStub()和newBlockingStub()方法獲取他們各自實(shí)例,他們主要用于客戶端的調(diào)用。在生成的request方法中,除了request本身的參數(shù),還有一個(gè)RpcController參數(shù),它用于處理在RpcChannel/BlockingRpcChannel調(diào)用中的狀態(tài)處理,如錯(cuò)誤處理等,使用它可以獲知此次調(diào)用是否出錯(cuò),錯(cuò)誤信息是什么等。在MyService中還定義了兩個(gè)靜態(tài)方法newReflectiveService/newReflectiveBlockingService,他們接收Interface/BlockingInterface實(shí)例,并返回Service/BlockingService的實(shí)現(xiàn)實(shí)例(暫時(shí)還沒有想到使用他們的場景)。

在MyService的RPC框架實(shí)現(xiàn)中,在服務(wù)器端,實(shí)現(xiàn)MyService.Interface/MyService.BlockingInterface接口,然后將它注冊到對RpcChannel/BlockingRpcChannel框架的實(shí)現(xiàn)中;在客戶端則創(chuàng)建一個(gè)RpcChannel/BlockingRpcChannel實(shí)例,傳入MyService.newStub()/MyService.newBlockingStub()方法獲取對應(yīng)的實(shí)例,然后使用這個(gè)Stub/BlockingStub實(shí)例調(diào)用相應(yīng)的方法即可。
posted on 2015-04-01 09:31
DLevin 閱讀(23688)
評論(1) 編輯 收藏 所屬分類:
Protobuf