基于Protobuf动态解析在Java中的应用 包含例子程序
最近在做protobuf相关的项目,其中用到了动态解析,网上看了下相关资料和博文都比较少,自己来写一个记录一下学习过程。
protocol buffers是结构化数据格式标准,提供序列化和反序列方法,用于存储和交换。语言中立,平台无关、可扩展。目前官方提供了c++、java、python api,也有其他语言的开源api(比如php)。可通过 .proto文件生成对应语言的类代码
如果已知protobuf内容对应的是哪个类对象,则可以直接使用反序列化方法搞定(xxx.parsefrom(inputstream)由二进制转换,textformat.merge(string, xxxbuilder)由文本转换)
而我们经常遇到的情况是,拿到一个被protobuf序列化的二进制内容,但不知道它的类型,无法获得对应的类对象。这种多见于需要处理各种各样未知的protobuf对象的系统。protobuf提供了动态解析机制来解决这个问题,它要求提供二进制内容的基础上,再提供对应类的descriptor对象,在解析时通过dynamicmessage类的成员方法来获得对象结果。
最后问题就是descriptor对象从哪里来?这是通过protoc --descriptor_set_out=$outputpath 命令生成descriptor文件,进而得到的。
代码如下:
cinema.proto
option java_package="com.liulei.cinema"; enum movietype{ children=1; adult=2; normal=3; ohter=4; } enum gender{ man=1; woman=2; other=3; } message movie{ required string name=1; required movietype type=2; optional int32 releasetimestamp=3; optional string description=4; } message customer{ required string name=1; optional gender gender=2; optional int32 birthdaytimestamp=3; } message ticket{ required int32 id=1; required movie movie=2; required customer customer=3; }
main.java
public static void main( string[] args ) { cinema.movie.builder moviebuilder = cinema.movie.newbuilder(); moviebuilder.setname("the shining"); moviebuilder.settype(cinema.movietype.adult); moviebuilder.setreleasetimestamp(327859200); system.out.println("dynamic message parse by proto file"); try { byte[] buffer3 = new byte[moviebuilder.build().getserializedsize()]; codedoutputstream codedoutputstream3 = codedoutputstream.newinstance(buffer3); try { moviebuilder.build().writeto(codedoutputstream3); system.out.println(buffer3); } catch (ioexception e) { e.printstacktrace(); } string protoccmd = "protoc --descriptor_set_out=cinema.description ./cinema.proto --proto_path=."; process process = runtime.getruntime().exec(protoccmd); process.waitfor(); int exitvalue = process.exitvalue(); if (exitvalue != 0) { system.out.println("protoc execute failed"); return; } descriptors.descriptor pbdescritpor = null; descriptorprotos.filedescriptorset descriptorset = descriptorprotos.filedescriptorset.parsefrom(new fileinputstream("./cinema.description")); for (descriptorprotos.filedescriptorproto fdp : descriptorset.getfilelist()) { descriptors.filedescriptor filedescriptor = descriptors.filedescriptor.buildfrom(fdp, new descriptors.filedescriptor[]{}); for (descriptors.descriptor descriptor : filedescriptor.getmessagetypes()) { if (descriptor.getname().equals("movie")) { system.out.println("movie descriptor found"); pbdescritpor = descriptor; break; } } } if (pbdescritpor == null) { system.out.println("no matched descriptor"); return; } dynamicmessage.builder pbbuilder = dynamicmessage.newbuilder(pbdescritpor); message pbmessage = pbbuilder.mergefrom(buffer3).build(); system.out.println(pbmessage); } catch (exception e) { system.out.println("exception"); e.printstacktrace(); } }
执行结果:
dynamic message parse from byte array
[b@597ccf6e
movie descriptor found
name: "the shining"
type: adult
releasetimestamp: 327859200
解释具体过程:
0.首先对.proto文件使用protoc命令,生成的descriptor文件中包含多个类对应的descriptor类信息(序列化的descriptorset内容)
1.首先取出序列化的descriptorset内容,filedescriptorset.parsefrom方法反序列化得到filedescriptorset对象
2.取出对应message类型的descriptor。
descriptorset成员方法getfilelist(),拿到多个filedescriptorproto对象,再构建对应filedescriptor。
filedescriptor的成员方法getmessagetypes()得到所有message的descriptor对象,找到对应名字的descriptor
3.用descriptor对象反序列化对象
构建dynamicmessage.builder对象builder,再调用builder的mergefrom/merge方法得到message对象
其中descriptor相关类:
descriptorprotos.descriptorset:protoc编译出来类文件中包含这个类,描述多个.proto文件中的类
descriptorprotos.filedescriptorproto:描述一个完整的.proto文件中的类
descriptorprotos.filedescriptor:由descriptorprotos.filedescriptorproto构建而来(buildfrom),描述1个完整.proto文件中的所有内容,包括message类型的descriptor和其他被导入文件的descriptor。
getmessagetypes()方法:返回list<descriptors.descriptor>。得到filedescriptor内,所有message类型直接儿子的descriptor列表
descriptorprotos.descriptor:描述一个message类型,通过getname()得到message的类名
以上这篇基于protobuf动态解析在java中的应用 包含例子程序就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。