linux java protobuf_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文件,进而得到的。代码如下:

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;

}

cinema.proto

-------------------------------------------------

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();

}

}

Main.java

执行结果:

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。得到FileDescriptor内,所有message类型直接儿子的Descriptor列表

DescriptorProtos.Descriptor:描述一个message类型,通过getName()得到message的类名

0b1331709591d260c1c78e86d0c51c18.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值