日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

protobuf在java应用中通过反射动态创建对象

發(fā)布時間:2023/12/4 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 protobuf在java应用中通过反射动态创建对象 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>

---恢復(fù)內(nèi)容開始---

最近編寫一個游戲用到protobuf數(shù)據(jù)格式進(jìn)行前后臺傳輸,苦于protobuf接受客戶端的數(shù)據(jù)時是需要數(shù)據(jù)類型的如xxx.parseForm(...),這樣就要求服務(wù)器在接受客戶端請求時必須知道客戶端傳遞的數(shù)據(jù)類型。由于客戶端的請求數(shù)據(jù)是多種多樣的,服務(wù)器端又不知道客戶端的請求到底是哪個類型,這樣就使得服務(wù)器端編程帶來很多麻煩,甚至寸步難行。難道就沒有解決辦法了嗎,答案當(dāng)然是有的。下面就說一下常用的方法。(在看本文之前建議先了解protobuf的一些基本語法,和基本用法)

1.第一種方法也是最簡單的方法,就是在整個應(yīng)用程序中只定義一個proto文件,那么所有的請求都是一種類型,那么服務(wù)器端就不用苦惱怎么解析請求數(shù)據(jù)了,因?yàn)椴还苣膫€請求數(shù)據(jù)都用同一個對象解析。如下面的列子:

首先貼一個PBMessage.proto文件

//客戶端請求以及服務(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; //請求的校驗(yàn)碼 optional int32 sessionId = 8;//當(dāng)前請求的標(biāo)示 } 如上述代碼,整個應(yīng)用都基于PBMessage.proto傳輸,注意到protobuf 語法中 optional修飾符,他表示這個字段是非必須的,也就是說對于客戶端的不同請求,只需要為它填充其請求時用到的字段的值即可,其他的字段的值就不用管了,這樣就可以模擬出各種請求來,那么接下來我們就用: PBMessage.parseForm(byte_PBMesage) // byte_PBMesage表示客戶端請求數(shù)據(jù) ,這樣請求的解析就完成了。同時我們注意到: (required int32 actionCode = 2; // 操作碼id) ,required表示該字段是必須的,前面請求已經(jīng)解析好了,在這里我們拿到 actionCode 就可以知道我們該用哪個Action事件來處理該請求了(前提是必須維護(hù)一張actionCode到Action的映射關(guān)系表:Map<int,Action>),至此整個請求的解析和處理都完成了。

接下來說一下第一種方式的優(yōu)缺點(diǎn),優(yōu)點(diǎn):整個應(yīng)用消息格式一致統(tǒng)一,操作簡單。缺點(diǎn):太統(tǒng)一,就不靈活,對于請求很少,消息格式很少的小型應(yīng)用倒還勉強(qiáng)能用,消息格式多的話再用這種方式就顯得臃腫,不便于管理,失去了程序設(shè)計(jì)的意義。

2.第二種方法,也是我本次用到的方法。苦于提議中方式的局限性,本人通過在網(wǎng)上收集資料以及查看protobuf java版的源碼,發(fā)現(xiàn)了一個折中的方式。首先我們看下protobuf源碼中提供的, DynamicMessage 類(顧名思義 動態(tài)消息類,眼前一亮有木有),它繼承了AbstractMessage類,比較一下和第一種方式創(chuàng)建的PBMessage類的區(qū)別 我們發(fā)現(xiàn)PBMessage類繼承了GeneratedMessage類,而GeneratedMessage類繼承了AbstractMessage類,至此我們發(fā)現(xiàn)了共同類AbstractMessage,再次證明了DynamicMessage 管用,同時我們再看看AbstractParser<MessageType>類(在PBMessage類中持有AbstractParser類的對象,并用其來解析請求數(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),在這里我們可以用一個等量關(guān)系比喻:DynamicMessage=AbstractMessage+ AbstractParser=PBMessage,也就是說DynamicMessage繼承AbstractMessage(請求消息對象)的同時又間接實(shí)現(xiàn)了AbstractParser(請求消息數(shù)據(jù)解析)對數(shù)據(jù)解析的功能?,F(xiàn)在我們唯一缺少的就是 Descriptors.Descriptor(對消息的描述)對象,這個對象該怎么拿到呢,在這里肯定的說,對于不同的.proto請求這里的Descriptors.Descriptor是不一樣的。在這里我們又回到PBMessage對象中,我們發(fā)現(xiàn)了其中有這樣一個方法:

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了,不是嗎。在這里重點(diǎn)來了,再來理解一下,首先有了 PBMessage對象(這里用其來做代表,可以使其他的.proto對象)就可以獲得 Descriptors.Descriptor 對象,有了Descriptors.Descriptor對象就可以創(chuàng)建DynamicMessage對象了,有了DynamicMessage就可以解析對應(yīng)請求了。下面看代碼:

//存放消息操作碼和消息對象 Map<Integer,Descriptor> descriptorMap=new Map<Integer,Descriptor>; //把消息描述對象添加進(jìn)來 descriptorMap.add(100,PBMessage.getDescriptor()); descriptorMap.add(xxx,xxx); 這樣Descriptor有了,其實(shí)還可以做得更好一點(diǎn),通過反射機(jī)制,Map里面只存放操作碼和對應(yīng)的proto對象類名,再通過反射方式創(chuàng)建proto對象在獲得其getDescriptor()方法。這樣就可以在配置文件中配置操作碼和proto對象的關(guān)系了。

好接下來我們來接受客戶端的請求試一下,

//客戶端偽代碼 client.send(byte_PBMessage); 然后服務(wù)器接受請求并解析,

//服務(wù)器偽代碼

byte[] date=server.accept();

//客戶端操作碼 int actionCode;

Descriptor descriptor=descriptorMap.get(actionCode);

//解析請求 DynamicMessage req=DynamicMessage.parseFrom(descriptor, date); 在這里我們發(fā)現(xiàn)似乎還少了點(diǎn)什么,好像 actionCode還不知道,怎么辦呢,好吧,我們在客戶端發(fā)送的請求消息頭上再加上個actionCode,即把操作碼和proto消息合并為一個新的請求發(fā)送給客戶端,請求2位為操作碼,那么現(xiàn)在客戶端應(yīng)該這么發(fā)送消息了:

//客戶端偽代碼 short actionCode=100;

//兩個字節(jié)來存放actionCode byte [] actionCodeByte=new byte [2];

// 轉(zhuǎn)換成字節(jié)流 actionCodeByte.set(actionCode.toByteArray());//偽代碼,請勿當(dāng)真

//帶請求頭的消息的總長度 int length=actionCodeByte.length+byte_PBMEssage.length;

byte [] messageByte=new byte[length];

//把操作碼和proto消息合并 messageByte=actionCodeByte+byte_PBMEssage;

client.send(messageByte); 下來是服務(wù)器了:

//服務(wù)器偽代碼 byte[] data=server.accept(); //把前兩位取出來 byte[] actionCodeByte=data.read(0,2);

// actionCode有了 int actionCode=actionCodeByte.readShort();

// 取出proto消息 byte[] byte_PBMessage=data.read(2,data.length); .....接下來就和前面的服務(wù)器偽代碼一樣了 DynamicMessage req=DynamicMessage.parseFrom(descriptorMap.get(actionCode, byte_PBMessage); .... 至此動態(tài)創(chuàng)建對象完成了,接下來就是按照第一種方式維護(hù)的ActionMap通過actionCode取到action來處理DynamicMessage 解析好的請求了

,當(dāng)然actionCode也可以換成actionName,類似的。到這里似乎差不多了,當(dāng)時始終不完美,因?yàn)槲覀冞€沒有把DynamicMessage 轉(zhuǎn)換成PBMessage對象,在后續(xù)的action里處理DynamicMessage總是不舒服,解決辦法是通過DynamicMessage對象獲得Descriptor對象,在獲得其所有字段名和值, 然后看一下這個地址的這篇文章(通過字段反射對象部分):http://liufei-fir.iteye.com/blog/1160700,通過反射來還原PBMessage,以上是經(jīng)過試驗(yàn)成功的,由于時間原因就不把源碼貼上來了。有什么問題希望大家指正。

轉(zhuǎn)載于:https://my.oschina.net/u/257088/blog/277461

總結(jié)

以上是生活随笔為你收集整理的protobuf在java应用中通过反射动态创建对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。