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

(0)

相关推荐

  • 正则表达式中环视的简单应用示例【基于java】

    本文实例讲述了正则表达式中环视的简单应用.分享给大家供大家参考,具体如下: 由于开发工作需要对文本中内容进行过滤,删除或替换掉一些无用的或不符合要求的信息.于是发现一个问题,某一类工程性文本中,用到很多英文写法相同.但含义不同的单位,需要将其分别转为真实含义对应的汉字.比如:"粘度为17s,移动距离为350厘米,要求混凝土必须内实外光.振捣时间为30s.",很明显第一个s是粘度的单位,第二s是时间单位,现在需要将文本中所有表示时间的s替换为"秒",在朋友指引下,发现

  • JAVA多线程并发下的单例模式应用

    单例模式应该是设计模式中比较简单的一个,也是非常常见的,但是在多线程并发的环境下使用却是不那么简单了,今天给大家分享一个我在开发过程中遇到的单例模式的应用. 首先我们先来看一下单例模式的定义: 一个类有且仅有一个实例,并且自行实例化向整个系统提供. 单例模式的要素: 1.私有的静态的实例对象 2.私有的构造函数(保证在该类外部,无法通过new的方式来创建对象实例) 3.公有的.静态的.访问该实例对象的方法 单例模式分为懒汉形和饿汉式 懒汉式: 应用刚启动的时候,并不创建实例,当外部调用该类的实例

  • java.lang.UnsatisfiedLinkError: %1 不是有效的Win32应用程序错误解决

    发现问题 最近工作中利用JNA 调用 dll 库时保错,错误如下: ///////////////// 通过 JNA 引入 DLL 库 //////////// /** * ID_FprCap.dll 负责指纹的采集, 指纹仪的初始化,设置等等 */ public interface ID_FprCap extends StdCallLibrary { ID_Fpr fpr = ID_Fpr.INSTANCE ; ID_FprCap fprCap = (ID_FprCap)Native.load

  • java应用领域分析

    如果你是一个初学者或者刚开始学习Java编程,你可能会思考Java编程确切地运用在哪些地方呢?除了Minecraft,你无法看到用Java编写的游戏吧?像AdobeAcrobat这样的桌面工具,Microsoft办公软件,这些都不是用Java编写的,甚至就连Linux或者Windows的操作系统也不是,那么人们到底在哪里使用了Java呢?Java到底有没有实际的应用程序呢? 好吧,其实你并不是第一个对此有疑问的人,初学Java或者在本科阶段选择学习Java作为一门编程语言的许多程序员都会有这个疑

  • 详解Java动态代理的实现及应用

    详解Java动态代理的实现及应用 Java动态代理其实写日常业务代码是不常用的,但在框架层一起RPC框架的客户端是非常常见及重要的.spring的核心思想aop的底层原理实现就使用到了java的动态代理技术. 使用代理可以实现对象的远程调用以及aop的实现. java的动态代理的实现,主要依赖InvoctionHandler(接口)和Proxy(类)这两个. 下面是一个例子 实现的代理的一般需要有个接口 package com.yasin.ProxyLearn; public interface

  • 浅谈java中unmodifiableList方法的应用场景

    java对象中primitive类型变量可以通过不提供set方法保证不被修改,但对象的List成员在提供get方法后,就可以随意add.remove改变其结构,这不是希望的结果.网上看了下,发现Collections的静态方法unmodifiableList可以达到目的.方法原型为:public static <T> List<T> unmodifiableList(List<? extends T> list);用法也很简单,传入一个List实例la,返回这个list

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

    最近在做ProtoBuf相关的项目,其中用到了动态解析,网上看了下相关资料和博文都比较少,自己来写一个记录一下学习过程. Protocol Buffers是结构化数据格式标准,提供序列化和反序列方法,用于存储和交换.语言中立,平台无关.可扩展.目前官方提供了C++.Java.Python API,也有其他语言的开源api(比如php).可通过 .proto文件生成对应语言的类代码 如果已知protobuf内容对应的是哪个类对象,则可以直接使用反序列化方法搞定(Xxx.parseFrom(inpu

  • 你真的理解Java中的ArrayList吗

    目录 1. 为什么需要ArrayList? 2. ArrayList底层是如何实现的? 3. 结合源码分析主要成员变量 4. 个人的一点总结 1. 为什么需要ArrayList? 图1 图2 记得在刚刚学习Java的时候,我们首先是学习了数组,这是我们学到的第一个可以存储多个对象的实例或者基本类型的具体值,数组存储的特点如下: 只能存储同种类型的数据. 在定义数组时,必须指定该数组的大小,并且在不改变数组的前提下,不可修改其长度. 以上特性就会导致很多弊端.比如:我们往往不希望数组只能存储一种数

  • Java 中ConcurrentHashMap的实现

    ConcurrentHashMap(简称CHM)是在Java 1.5作为Hashtable的替代选择新引入的,是concurrent包的重要成员.在Java 1.5之前,如果想要实现一个可以在多线程和并发的程序中安全使用的Map,只能在HashTable和synchronized Map中选择,因为HashMap并不是线程安全的.但再引入了CHM之后,我们有了更好的选择.CHM不但是线程安全的,而且比HashTable和synchronizedMap的性能要好.相对于HashTable和sync

  • 基于JAVA中的四种JSON解析方式详解

    我们在日常开发中少不了和JSON数据打交道,那么我们来看看JAVA中常用的JSON解析方式. 1.JSON官方 2.GSON 3.FastJSON 4.jackson JSON操作涉及到的类: public class Student { private int id; private String name; private int age; public int getId() { return id; } public void setId(int id) { this.id = id;

  • 深入解析Java中的Class Loader类加载器

    类加载的过程 类加载器的主要工作就是把类文件加载到JVM中.如下图所示,其过程分为三步: 1.加载:定位要加载的类文件,并将其字节流装载到JVM中: 2.链接:给要加载的类分配最基本的内存结构保存其信息,比如属性,方法以及引用的类.在该阶段,该类还处于不可用状态: (1)验证:对加载的字节流进行验证,比如格式上的,安全方面的: (2)内存分配:为该类准备内存空间来表示其属性,方法以及引用的类: (3)解析:加载该类所引用的其它类,比如父类,实现的接口等. 3.初始化:对类变量进行赋值. 类加载器

  • Java中动态规则的实现方式示例详解

    背景 业务系统在应用过程中,有时候要处理"经常变化"的部分,这部分需求可能是"业务规则",也可能是"不同的数据处理逻辑",这部分动态规则的问题,往往需要可配置,并对性能和实时性有一定要求. Java不是解决动态层问题的理想语言,在实践中发现主要有以下几种方式可以实现: 表达式语言(expression language) 动态语言(dynamic/script language language),如Groovy 规则引擎(rule engine

  • GC参考手册二java中垃圾回收原理解析

    内存碎片整理 每次执行清除(sweeping), JVM 都必须保证不可达对象占用的内存能被回收重用.但这(最终)有可能会产生内存碎片(类似于磁盘碎片), 进而引发两个问题: 写入操作越来越耗时, 因为寻找一块足够大的空闲内存会变得非常麻烦. 在创建新对象时, JVM在连续的块中分配内存.如果碎片问题很严重, 直至没有空闲片段能存放下新创建的对象,就会发生内存分配错误(allocation error). 要避免这类问题,JVM 必须确保碎片问题不失控.因此在垃圾收集过程中, 不仅仅是标记和清除

  • 基于Java中的StringTokenizer类详解(推荐)

    StringTokenizer是字符串分隔解析类型,属于:Java.util包. 1.StringTokenizer的构造函数 StringTokenizer(String str):构造一个用来解析str的StringTokenizer对象.java默认的分隔符是"空格"."制表符('\t')"."换行符('\n')"."回车符('\r')". StringTokenizer(String str,String delim)

  • Java中使用开源库JSoup解析HTML文件实例

    HTML是WEB的核心,互联网中你看到的所有页面都是HTML,不管它们是由JavaScript,JSP,PHP,ASP或者是别的什么WEB技术动态生成的.你的浏览器会去解析HTML并替你去渲染它们.不过如果你需要自己在Java程序中解析HTML文档并查找某些元素,标签,属性或者检查某个特定的元素是否存在的话,那又该如何呢?如果你已经使用Java编程多年了,我相信你肯定试过去解析XML,也使用过类似DOM或者SAX这样的解析器,不过很有可能你从未进行过任何的HTML解析的工作.更讽刺的是,在Jav

  • Java中对XML的解析详解

    先简单说下前三种方式: DOM方式:个人理解类似.net的XmlDocument,解析的时候效率不高,占用内存,不适合大XML的解析: SAX方式:基于事件的解析,当解析到xml的某个部分的时候,会触发特定事件,可以在自定义的解析类中定义当事件触发时要做得事情:个人感觉一种很另类的方式,不知道.Net体系下是否有没有类似的方式? StAX方式:个人理解类似.net的XmlReader方式,效率高,占用内存少,适用大XML的解析: 不过SAX方式之前也用过,本文主要介绍JAXB,这里只贴下主要代码

随机推荐