欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于Protobuf动态解析在Java中的应用 包含例子程序

程序员文章站 2023-11-22 16:29:52
最近在做protobuf相关的项目,其中用到了动态解析,网上看了下相关资料和博文都比较少,自己来写一个记录一下学习过程。 protocol buffers是结构化数据...

最近在做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中的应用 包含例子程序就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。