protobuf在java应用中通过反射动态创建对象
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
---恢復(fù)內(nèi)容開(kāi)始---
最近編寫(xiě)一個(gè)游戲用到protobuf數(shù)據(jù)格式進(jìn)行前后臺(tái)傳輸,苦于protobuf接受客戶端的數(shù)據(jù)時(shí)是需要數(shù)據(jù)類型的如xxx.parseForm(...),這樣就要求服務(wù)器在接受客戶端請(qǐng)求時(shí)必須知道客戶端傳遞的數(shù)據(jù)類型。由于客戶端的請(qǐng)求數(shù)據(jù)是多種多樣的,服務(wù)器端又不知道客戶端的請(qǐng)求到底是哪個(gè)類型,這樣就使得服務(wù)器端編程帶來(lái)很多麻煩,甚至寸步難行。難道就沒(méi)有解決辦法了嗎,答案當(dāng)然是有的。下面就說(shuō)一下常用的方法。(在看本文之前建議先了解protobuf的一些基本語(yǔ)法,和基本用法)
1.第一種方法也是最簡(jiǎn)單的方法,就是在整個(gè)應(yīng)用程序中只定義一個(gè)proto文件,那么所有的請(qǐng)求都是一種類型,那么服務(wù)器端就不用苦惱怎么解析請(qǐng)求數(shù)據(jù)了,因?yàn)椴还苣膫€(gè)請(qǐng)求數(shù)據(jù)都用同一個(gè)對(duì)象解析。如下面的列子:
首先貼一個(gè)PBMessage.proto文件
//客戶端請(qǐng)求以及服務(wù)端響應(yīng)數(shù)據(jù)協(xié)議 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; //提交或響應(yīng)的數(shù)據(jù) optional DataMsg dataMsg = 6; //服務(wù)器端推送數(shù)據(jù) optional string sessionKey = 7; //請(qǐng)求的校驗(yàn)碼 optional int32 sessionId = 8;//當(dāng)前請(qǐng)求的標(biāo)示 } 如上述代碼,整個(gè)應(yīng)用都基于PBMessage.proto傳輸,注意到protobuf 語(yǔ)法中 optional修飾符,他表示這個(gè)字段是非必須的,也就是說(shuō)對(duì)于客戶端的不同請(qǐng)求,只需要為它填充其請(qǐng)求時(shí)用到的字段的值即可,其他的字段的值就不用管了,這樣就可以模擬出各種請(qǐng)求來(lái),那么接下來(lái)我們就用: PBMessage.parseForm(byte_PBMesage) // byte_PBMesage表示客戶端請(qǐng)求數(shù)據(jù) ,這樣請(qǐng)求的解析就完成了。同時(shí)我們注意到: (required int32 actionCode = 2; // 操作碼id) ,required表示該字段是必須的,前面請(qǐng)求已經(jīng)解析好了,在這里我們拿到 actionCode 就可以知道我們?cè)撚媚膫€(gè)Action事件來(lái)處理該請(qǐng)求了(前提是必須維護(hù)一張actionCode到Action的映射關(guān)系表:Map<int,Action>),至此整個(gè)請(qǐng)求的解析和處理都完成了。
接下來(lái)說(shuō)一下第一種方式的優(yōu)缺點(diǎn),優(yōu)點(diǎn):整個(gè)應(yīng)用消息格式一致統(tǒng)一,操作簡(jiǎn)單。缺點(diǎn):太統(tǒng)一,就不靈活,對(duì)于請(qǐng)求很少,消息格式很少的小型應(yīng)用倒還勉強(qiáng)能用,消息格式多的話再用這種方式就顯得臃腫,不便于管理,失去了程序設(shè)計(jì)的意義。
2.第二種方法,也是我本次用到的方法。苦于提議中方式的局限性,本人通過(guò)在網(wǎng)上收集資料以及查看protobuf java版的源碼,發(fā)現(xiàn)了一個(gè)折中的方式。首先我們看下protobuf源碼中提供的, DynamicMessage 類(顧名思義 動(dòng)態(tài)消息類,眼前一亮有木有),它繼承了AbstractMessage類,比較一下和第一種方式創(chuàng)建的PBMessage類的區(qū)別 我們發(fā)現(xiàn)PBMessage類繼承了GeneratedMessage類,而GeneratedMessage類繼承了AbstractMessage類,至此我們發(fā)現(xiàn)了共同類AbstractMessage,再次證明了DynamicMessage 管用,同時(shí)我們?cè)倏纯碅bstractParser<MessageType>類(在PBMessage類中持有AbstractParser類的對(duì)象,并用其來(lái)解析請(qǐng)求數(shù)據(jù)),它繼承了Parser<MessageType>接口,看看其部分方法:
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(); }
發(fā)現(xiàn)了他們方法的相似點(diǎn),在這里我們可以用一個(gè)等量關(guān)系比喻:DynamicMessage=AbstractMessage+ AbstractParser=PBMessage,也就是說(shuō)DynamicMessage繼承AbstractMessage(請(qǐng)求消息對(duì)象)的同時(shí)又間接實(shí)現(xiàn)了AbstractParser(請(qǐng)求消息數(shù)據(jù)解析)對(duì)數(shù)據(jù)解析的功能。現(xiàn)在我們唯一缺少的就是 Descriptors.Descriptor(對(duì)消息的描述)對(duì)象,這個(gè)對(duì)象該怎么拿到呢,在這里肯定的說(shuō),對(duì)于不同的.proto請(qǐng)求這里的Descriptors.Descriptor是不一樣的。在這里我們又回到PBMessage對(duì)象中,我們發(fā)現(xiàn)了其中有這樣一個(gè)方法:
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; } } } 這不就是我們苦苦尋找的東西嗎,通過(guò)這個(gè)方法就可以拿到Descriptor了,不是嗎。在這里重點(diǎn)來(lái)了,再來(lái)理解一下,首先有了 PBMessage對(duì)象(這里用其來(lái)做代表,可以使其他的.proto對(duì)象)就可以獲得 Descriptors.Descriptor 對(duì)象,有了Descriptors.Descriptor對(duì)象就可以創(chuàng)建DynamicMessage對(duì)象了,有了DynamicMessage就可以解析對(duì)應(yīng)請(qǐng)求了。下面看代碼:
//存放消息操作碼和消息對(duì)象 Map<Integer,Descriptor> descriptorMap=new Map<Integer,Descriptor>; //把消息描述對(duì)象添加進(jìn)來(lái) descriptorMap.add(100,PBMessage.getDescriptor()); descriptorMap.add(xxx,xxx); 這樣Descriptor有了,其實(shí)還可以做得更好一點(diǎn),通過(guò)反射機(jī)制,Map里面只存放操作碼和對(duì)應(yīng)的proto對(duì)象類名,再通過(guò)反射方式創(chuàng)建proto對(duì)象在獲得其getDescriptor()方法。這樣就可以在配置文件中配置操作碼和proto對(duì)象的關(guān)系了。
好接下來(lái)我們來(lái)接受客戶端的請(qǐng)求試一下,
//客戶端偽代碼 client.send(byte_PBMessage); 然后服務(wù)器接受請(qǐng)求并解析,
//服務(wù)器偽代碼
byte[] date=server.accept();
//客戶端操作碼 int actionCode;
Descriptor descriptor=descriptorMap.get(actionCode);
//解析請(qǐng)求 DynamicMessage req=DynamicMessage.parseFrom(descriptor, date); 在這里我們發(fā)現(xiàn)似乎還少了點(diǎn)什么,好像 actionCode還不知道,怎么辦呢,好吧,我們?cè)诳蛻舳税l(fā)送的請(qǐng)求消息頭上再加上個(gè)actionCode,即把操作碼和proto消息合并為一個(gè)新的請(qǐng)求發(fā)送給客戶端,請(qǐng)求2位為操作碼,那么現(xiàn)在客戶端應(yīng)該這么發(fā)送消息了:
//客戶端偽代碼 short actionCode=100;
//兩個(gè)字節(jié)來(lái)存放actionCode byte [] actionCodeByte=new byte [2];
// 轉(zhuǎn)換成字節(jié)流 actionCodeByte.set(actionCode.toByteArray());//偽代碼,請(qǐng)勿當(dāng)真
//帶請(qǐng)求頭的消息的總長(zhǎng)度 int length=actionCodeByte.length+byte_PBMEssage.length;
byte [] messageByte=new byte[length];
//把操作碼和proto消息合并 messageByte=actionCodeByte+byte_PBMEssage;
client.send(messageByte); 下來(lái)是服務(wù)器了:
//服務(wù)器偽代碼 byte[] data=server.accept(); //把前兩位取出來(lái) byte[] actionCodeByte=data.read(0,2);
// actionCode有了 int actionCode=actionCodeByte.readShort();
// 取出proto消息 byte[] byte_PBMessage=data.read(2,data.length); .....接下來(lái)就和前面的服務(wù)器偽代碼一樣了 DynamicMessage req=DynamicMessage.parseFrom(descriptorMap.get(actionCode, byte_PBMessage); .... 至此動(dòng)態(tài)創(chuàng)建對(duì)象完成了,接下來(lái)就是按照第一種方式維護(hù)的ActionMap通過(guò)actionCode取到action來(lái)處理DynamicMessage 解析好的請(qǐng)求了
,當(dāng)然actionCode也可以換成actionName,類似的。到這里似乎差不多了,當(dāng)時(shí)始終不完美,因?yàn)槲覀冞€沒(méi)有把DynamicMessage 轉(zhuǎn)換成PBMessage對(duì)象,在后續(xù)的action里處理DynamicMessage總是不舒服,解決辦法是通過(guò)DynamicMessage對(duì)象獲得Descriptor對(duì)象,在獲得其所有字段名和值, 然后看一下這個(gè)地址的這篇文章(通過(guò)字段反射對(duì)象部分):http://liufei-fir.iteye.com/blog/1160700,通過(guò)反射來(lái)還原PBMessage,以上是經(jīng)過(guò)試驗(yàn)成功的,由于時(shí)間原因就不把源碼貼上來(lái)了。有什么問(wèn)題希望大家指正。
轉(zhuǎn)載于:https://my.oschina.net/u/257088/blog/277461
總結(jié)
以上是生活随笔為你收集整理的protobuf在java应用中通过反射动态创建对象的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Scribefire发CSDN博客
- 下一篇: LeetCode --- Valid P