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

歡迎訪問(wèn) 生活随笔!

生活随笔

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

编程问答

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

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

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)題。

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