protobuf在java應用中經過反射動態建立對象

最近編寫一個遊戲用到protobuf數據格式進行先後臺傳輸,苦於protobuf接受客戶端的數據時是須要數據類型的如xxx.parseForm(…),這樣就要求服務器在接受客戶端請求時必須知道客戶端傳遞的數據類型。因爲客戶端的請求數據是多種多樣的,服務器端又不知道客戶端的請求究竟是哪一個類型,這樣就使得服務器端編程帶來不少麻煩,甚至步履維艱。難道就沒有解決辦法了嗎,答案固然是有的。下面就說一下經常使用的方法。(在看本文以前建議先了解protobuf的一些基本語法,和基本用法) 1.第一種方法也是最簡單的方法,就是在整個應用程序中只定義一個proto文件,那麼全部的請求都是一種類型,那麼服務器端就不用苦惱怎麼解析請求數據了,由於無論哪一個請求數據都用同一個對象解析。以下面的列子: 首先貼一個PBMessage.proto文件 //客戶端請求以及服務端響應數據協議 option java_outer_classname = 「PBMessageProto」; package com.ppsea.message; import 「main/resources/message/DataMsg.proto」; message PBMessage{ optional int32 playerId = 1; //玩家id required int32 actionCode = 2; //操做碼id optional bytes data = 5; //提交或響應的數據 optional DataMsg dataMsg = 6; //服務器端推送數據 optional string sessionKey = 7; //請求的校驗碼 optional int32 sessionId = 8;//當前請求的標示 } 如上述代碼,整個應用都基於PBMessage.proto傳輸,注意到protobuf 語法中 optional修飾符,他表示這個字段是非必須的,也就是說對於客戶端的不一樣請求,只須要爲它填充其請求時用到的字段的值便可,其餘的字段的值就不用管了,這樣就能夠模擬出各類請求來,那麼接下來咱們就用: PBMessage.parseForm(byte_PBMesage) // byte_PBMesage表示客戶端請求數據 ,這樣請求的解析就完成了。同時咱們注意到: (required int32 actionCode = 2; // 操做碼id) ,required表示該字段是必須的,前面請求已經解析好了,在這裏咱們拿到 actionCode 就能夠知道咱們該用哪一個Action事件來處理該請求了(前提是必須維護一張actionCode到Action的映射關係表:Map ),至此整個請求的解析和處理都完成了。 接下來講一下第一種方式的優缺點,優勢:整個應用消息格式一致統一,操做簡單。缺點:太統一,就不靈活,對於請求不多,消息格式不多的小型應用倒還勉強能用,消息格式多的話再用這種方式就顯得臃腫,不便於管理,失去了程序設計的意義。 2.第二種方法,也是我本次用到的方法。苦於提議中方式的侷限性,本人經過在網上收集資料以及查看protobuf java版的源碼,發現了一個折中的方式。首先咱們看下protobuf源碼中提供的, DynamicMessage 類(顧名思義 動態消息類,眼前一亮有木有),它繼承了AbstractMessage類,比較一下和第一種方式建立的PBMessage類的區別 咱們發現PBMessage類繼承了GeneratedMessage類,而GeneratedMessage類繼承了AbstractMessage類,至此咱們發現了共同類AbstractMessage,再次證實了DynamicMessage 管用,同時咱們再看看AbstractParser 類(在PBMessage類中持有AbstractParser類的對象,並用其來解析請求數據),它繼承了Parser 接口,看看其部分方法: public abstract MessageType parseFrom(byte[] paramArrayOfByte) throws InvalidProtocolBufferException; public abstract MessageType parseFrom(InputStream paramInputStream) throws InvalidProtocolBufferException; public abstract MessageType parseFrom(InputStream paramInputStream,ExtensionRegistryLite paramExtensionRegistryLite) throws InvalidProtocolBufferException; ,再看看DynamicMessage 裏面提供的方法: public static DynamicMessage parseFrom(Descriptors.Descriptor type,byte[] data) throws InvalidProtocolBufferException { return ((Builder) newBuilder(type).mergeFrom(data)).buildParsed(); } public static DynamicMessage parseFrom(Descriptors.Descriptor type,byte[] data, ExtensionRegistry extensionRegistry)throws InvalidProtocolBufferException { return ((Builder) newBuilder(type).mergeFrom(data, extensionRegistry)).buildParsed(); } public static DynamicMessage parseFrom(Descriptors.Descriptor type,InputStream input) throws IOException { return ((Builder) newBuilder(type).mergeFrom(input)).buildParsed(); } public static DynamicMessage parseFrom(Descriptors.Descriptor type,InputStream input, ExtensionRegistry extensionRegistry)throws IOException { return ((Builder) newBuilder(type).mergeFrom(input, extensionRegistry)).buildParsed(); } 發現了他們方法的類似點,在這裏咱們能夠用一個等量關係比喻:DynamicMessage=AbstractMessage+ AbstractParser=PBMessage,也就是說DynamicMessage繼承AbstractMessage(請求消息對象)的同時又間接實現了AbstractParser(請求消息數據解析)對數據解析的功能。如今咱們惟一缺乏的就是 Descriptors.Descriptor(對消息的描述)對象,這個對象該怎麼拿到呢,在這裏確定的說,對於不一樣的.proto請求這裏的Descriptors.Descriptor是不同的。在這裏咱們又回到PBMessage對象中,咱們發現了其中有這樣一個方法: public final class PBMessageProto { ..................//此處省略若干行public static final class PBMessage extendscom.google.protobuf.GeneratedMessage implements PBMessageOrBuilder { ………..//此處省略若干行 public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { return com.ppsea.message.PBMessageProto.internal_static_com_ppsea_message_PBMessage_descriptor; } } } 這不就是咱們苦苦尋找的東西嗎,經過這個方法就能夠拿到Descriptor了,不是嗎。在這裏重點來了,再來理解一下,首先有了 PBMessage對象(這裏用其來作表明,可使其餘的.proto對象)就能夠得到 Descriptors.Descriptor 對象,有了Descriptors.Descriptor對象就能夠建立DynamicMessage對象了,有了DynamicMessage就能夠解析對應請求了。下面看代碼: //存放消息操做碼和消息對象 Map descriptorMap=new Map ; //把消息描述對象添加進來 descriptorMap.add(100,PBMessage.getDescriptor()); descriptorMap.add(xxx,xxx); 這樣Descriptor有了,其實還能夠作得更好一點,經過反射機制,Map裏面只存放操做碼和對應的proto對象類名,再經過反射方式建立proto對象在得到其getDescriptor()方法。這樣就能夠在配置文件中配置操做碼和proto對象的關係了。 好接下來咱們來接受客戶端的請求試一下, //客戶端僞代碼 client.send(byte_PBMessage); 而後服務器接受請求並解析, //服務器僞代碼 byte[] date=server.accept(); //客戶端操做碼 int actionCode; Descriptor descriptor=descriptorMap.get(actionCode); //解析請求 DynamicMessage req=DynamicMessage.parseFrom(descriptor, date); 在這裏咱們發現彷佛還少了點什麼,好像 actionCode還不知道,怎麼辦呢,好吧,咱們在客戶端發送的請求消息頭上再加上個actionCode,即把操做碼和proto消息合併爲一個新的請求發送給客戶端,請求2位爲操做碼,那麼如今客戶端應該這麼發送消息了: //客戶端僞代碼 short actionCode=100; //兩個字節來存放actionCode byte [] actionCodeByte=new byte [2]; // 轉換成字節流 actionCodeByte.set(actionCode.toByteArray());//僞代碼,請勿當真 //帶請求頭的消息的總長度 int length=actionCodeByte.length+byte_PBMEssage.length; byte [] messageByte=new byte[length]; //把操做碼和proto消息合併 messageByte=actionCodeByte+byte_PBMEssage; client.send(messageByte); 下來是服務器了: //服務器僞代碼 byte[] data=server.accept(); //把前兩位取出來 byte[] actionCodeByte=data.read(0,2); // actionCode有了 int actionCode=actionCodeByte.readShort(); // 取出proto消息 byte[] byte_PBMessage=data.read(2,data.length); …..接下來就和前面的服務器僞代碼同樣了 DynamicMessage req=DynamicMessage.parseFrom(descriptorMap.get(actionCode, byte_PBMessage); …. 至此動態建立對象完成了,接下來就是按照第一種方式維護的ActionMap經過actionCode取到action來處理DynamicMessage 解析好的請求了 ,固然actionCode也能夠換成actionName,相似的。到這裏彷佛差很少了,當時始終不完美,由於咱們尚未把DynamicMessage 轉換成PBMessage對象,在後續的action裏處理DynamicMessage老是不舒服,解決辦法是經過DynamicMessage對象得到Descriptor對象,在得到其全部字段名和值, 而後看一下這個地址的這篇文章(經過字段反射對象部分):http://liufei-fir.iteye.com/blog/1160700,經過反射來還原PBMessage,以上是通過試驗成功的,因爲時間緣由就不把源碼貼上來了。有什麼問題但願你們指正。java

相關文章
相關標籤/搜索