protobuf在java应用中通过反射动态创建对象
2019独角兽企业重金招聘Python工程师标准>>>
---恢复内容开始---
最近编写一个游戏用到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<int,Action>),至此整个请求的解析和处理都完成了。
接下来说一下第一种方式的优缺点,优点:整个应用消息格式一致统一,操作简单。缺点:太统一,就不灵活,对于请求很少,消息格式很少的小型应用倒还勉强能用,消息格式多的话再用这种方式就显得臃肿,不便于管理,失去了程序设计的意义。
2.第二种方法,也是我本次用到的方法。苦于提议中方式的局限性,本人通过在网上收集资料以及查看protobuf java版的源码,发现了一个折中的方式。首先我们看下protobuf源码中提供的, DynamicMessage 类(顾名思义 动态消息类,眼前一亮有木有),它继承了AbstractMessage类,比较一下和第一种方式创建的PBMessage类的区别 我们发现PBMessage类继承了GeneratedMessage类,而GeneratedMessage类继承了AbstractMessage类,至此我们发现了共同类AbstractMessage,再次证明了DynamicMessage 管用,同时我们再看看AbstractParser<MessageType>类(在PBMessage类中持有AbstractParser类的对象,并用其来解析请求数据),它继承了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(); }
发现了他们方法的相似点,在这里我们可以用一个等量关系比喻: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<Integer,Descriptor> descriptorMap=new Map<Integer,Descriptor>; //把消息描述对象添加进来 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,以上是经过试验成功的,由于时间原因就不把源码贴上来了。有什么问题希望大家指正。
转载于:https://my.oschina.net/u/257088/blog/277461
protobuf在java应用中通过反射动态创建对象相关推荐
- protobuf java 自动反射_protobuf在java应用中通过反射动态创建对象
最近编写一个游戏用到protobuf数据格式进行前后台传输,苦于protobuf接受客户端的数据时是需要数据类型的如xxx.parseForm(-),这样就要求服务器在接受客户端请求时必须知道客户端传 ...
- Java 中使用反射来创建对象、调用方法
Java 中使用反射来创建对象.调用方法 反射创建对象 反射调用方法 反射调用私有方法 反射调用可变参私有方法 反射调用的方法自身可以抛出异常的情形 假设已有下面的类: import java.l ...
- C# 利用反射动态创建对象[摘录]
摘自:http://hi.baidu.com/yangyuhang/blog/item/f12ea90e13f214e336d12250.html 在VS.Net中,有很多种方法动态调用对象的构造函数 ...
- JAVA语言中的反射机制
在Java 运行时 环境中,对于任意一个类,能否知道这个类有哪些属性和方法? 对于任意一个对象,能否调用他的方法?这些答案是肯定的,这种动态获取类的信息,以及动态调用类的方法的功能来源于JAV ...
- 利用.Net中的反射动态调用方法
.Net中的反射功能是极其强大的,本篇先用他来动态调用方法 看如下一个类 Public Class A { public void InvokeMethod(string methodname) { ...
- 反射动态创建对象_Json为例
1. 取得数据类型Type 方式一:Type.GetType("类型全名"); 适合于类型的名称已知 方式二:obj.GetType(); 适合于类型名未知,类型未知,存在已有对象 ...
- protobuf在java中使用_记录:Protocol Buffers(protobuf)在Java开发中使用
1.编写一个.proto文件命名为:addressbook.proto,该文件内容来自protocal-buffers官网 2.使用protoc-2.6.0-win32.zip解压后的protoc.e ...
- java 动态调用函数_深入讲解Java语言中的函数动态调用方式
相 信 有 不 少 人 使 用C 语 言 的 函 数 指 针 实 现 过 函 数 的 动 态 调 用. 适 当 地 运 用 函 数 动 态 调 用 功 能 不 仅 能 减 少 代 码 数 量, 而 且 ...
- 用反射动态创建对象,类型无法转换的困惑
企图:从XML读出节点并动态创建形成对象. 参考:http://www.cnblogs.com/Arlen/archive/2007/05/30/765506.html 工厂中的代码: public ...
最新文章
- Strom序列化机制
- c语言中count的头文件,求助C语言大佬 , 只会写到一个.c文件里 ,不会用.h头文件...
- H3C S5120-52P-WiNet交换机配置
- [导入]javascript身份证号码验证函数支持带x
- python语言学习笔记整理
- [Delphi]ListView基本用法大全
- C# 杀掉指定进程
- azure上传代码_深入了解Azure Data Studio:更多代码和更少GUI
- 当编程之于爱情就如鱼之于熊掌般不可兼得时,该怎么办?
- 【译】WebSocket协议第五章——数据帧(Data Framing)
- 2021-09-06Cross-product transformation
- 计算日期在当月是第几周-【自然周(每月第一个周一为该月第一周)做法以及1号为第一周做法】
- CentOS7常见问题
- cad解除块的快捷命令_CAD怎么使用快捷命令快速创建永久块?
- 国内外比较出名的聚合路由器都有哪些?
- 动作冒险类游戏:星球流浪者Planet Nomads for mac
- ORA-02085: database link %s connects to %s 解决思路
- 猜音谜——倒放音频挑战赛
- 新海诚画集[秒速5センチメートル:樱花抄·遠野家]...
- 2012搜狗校园招聘笔试题(2)