本文档为java编程人员使用protocol buffer提供了一个基本的介绍,通过一个简单的例程进行介绍。通过本文,你可以了解到如下信息:
1、在一个.proto文件中定义一个信息格式.
2、使用protoc命令进行编译,生成java代码.
3、使用Java protocol buffer API进行读写操作.
l 定义proto文件
以一个地址薄为例,从建立一个.proto文件开始,为需要序列化的数据接口加入一个message属性,在message里面,为每一个字段指定名称和类型,如下所示:
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
message AddressBook {
repeated Person person = 1;
}
|
正如你所见, c++和Java中message定义的语法类似,下面我们来看看每个部分的意义:
为了避免命名冲突,.proto文件以包声明开始,在java中除了特别指定一个java_package属性,否则包名一般为Java的包。正像上面的例子,虽然提供了java_package属性,你通常还是应该定义package属性以避免在ProtocolBuffers中命名冲突。包声明以后,有两个Java属性:java_package和java_outer_classname。java_package表示生成的Java代码的包,如果没有指定,编译器会根据package属性确定包名。java_outer_classname属性定义生成文件的类名。如果没有指定,会根据文件名进行转换,如:"my_proto.proto"缺省会使用MyProto作为外部类名。
接下来是定义message属性,一个message是包含了各种类型字段的聚集。有很多标准的变量类型可以使用,包括:bool,int32,float,double和string。你也可以使用其他的message作为字段类型。正像例子中的Person包含了PhoneNumber,而AddressBook包含了Persion。甚至可以在message内部定义message,例如:PhoneNumber就是在Persion里面定义的。你还可以定义enum类型,正像指定电话号码类型的MOBILE、HOME、WORK。
其中“=1”,“=2”表示每个元素的标识号,它会用在二进制编码中对域的标识。标识号1-15由于使用时会比那些高的标识号少一个字节,从最优化角度考虑,可以将其使用在一些较常用的或repeated元素上,对于16以上的则使用在不常用的或optional的元素上。对于repeated的每个元素都需要重复编码该标识号,所以repeated的域进行优化来说是最显示的。
每个字段必须提供一个修饰词:
Ø required:表示字段必须提供,不能为空。否则message会被认为是未初始化的,试图build未初始化的message会抛出RuntimeException。解析未初始化的message会抛出IOException。除此之外,一个required字段与optional字段完全相同。
Ø optional:可选字段,可以设置也可以不设置。如果没有设置,会设置一个缺省值。可以指定一个缺省值,正像电话号码的type字段。否则,使用系统的缺省值:数字类型缺省为0;字符类型缺省为空串;逻辑类型缺省为false;对于嵌入的message,缺省值通常是message的实例或原型。
Ø repeated:字段可以被重复(包括0),可等同于动态数组或列表。其中存储的值列表的顺序是被保留的。
Required修饰的字段是永久性的,在使用该修饰符时一定要特别小心。如果在以后想要修改required域为optional域时会出现问题。对于访问旧接口的用户来说没有该字段时,将会认为是不合法的访问,将会被拒绝或丢弃。其中google的一些工程师给出的建议是如果不是必须,就尽量少用required修饰符。
l 编译Protocol Buffers文件
既然现在已经有了.proto文件,接下来就需要利用编译器protoc对.proto文件进行编译,生成具体的java类。就可以读取及写入AddressBook、Person及PersonNumber消息了。
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto
|
$SRC_DIR :表示.proto文件所在目录;$DST_DIR:生成的java代码的文件夹。
编译成功后,会在指定的目录下生成Java代码文件,包含了对属性的操作,下一步就可以通过API进行数据的读写了。
l Protocol Buffer API使用
接下来具体看一下所生成的java代码及其中的方法。在AddressBookProtos.java中可以看出,其中的内部类对应的是addressbook.proto中定义的格式。每个类都有它自己的Builder类,通过它即可以创建该类的实例。你可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/javatutorial.html#builders中查阅到更多关于builder的信息。
Messages和Builders都会为每个域创建自动的访问方法,其中messages只有getters,而builders有getters和setters。下面是Person类message的访问方法:
// required string name = 1; public boolean hasName(); public String getName();
// required int32 id = 2; public boolean hasId(); public int getId();
// optional string email = 3; public boolean hasEmail(); public String getEmail();
// repeated .tutorial.Person.PhoneNumber phone = 4; public List<PhoneNumber> getPhoneList(); public int getPhoneCount(); public PhoneNumber getPhone(int index);
|
Person类builder的访问方法(Person.Builder):
// required string name = 1; public boolean hasName(); public java.lang.String getName(); public Builder setName(String value); public Builder clearName();
// required int32 id = 2; public boolean hasId(); public int getId(); public Builder setId(int value); public Builder clearId();
// optional string email = 3; public boolean hasEmail(); public String getEmail(); public Builder setEmail(String value); public Builder clearEmail();
// repeated .tutorial.Person.PhoneNumber phone = 4; public List<PhoneNumber> getPhoneList(); public int getPhoneCount(); public PhoneNumber getPhone(int index); public Builder setPhone(int index, PhoneNumber value); public Builder addPhone(PhoneNumber value); public Builder addAllPhone(Iterable<PhoneNumber> value); public Builder clearPhone();
|
正如你所见,对于每个域都有简单的javabean风格的getters和setters。对于具有单一值的类型,有has方法用来表示该值是否有设置。当然也可以通过clear方法来将该字段的值清空。
重复域也有额外的方法,如count方法用来统计当前重复域的大小,getters和setters用于根据索引来获取或设置值。add方法用于将一个新元素添加到重复域中,addAll方法则将一组元素添加到重复域中。
上述示例中访问方法的名称采用了驼峰式命名,对应在.proto文件中采用的是小写字母+下划线的命名。这种转换是由protoc编译器自动完成的,我们只需要按照这种规约定义.proto文件即可。
l 枚举和内部类
生成的代码包含了一个枚举类型PhoneType,它属于Person的内部类:
public static enum PhoneType { MOBILE(0, 0), HOME(1, 1), WORK(2, 2), ; ... }
|
PhoneNumber也是作为Person的一个内部类而产生的。
l Builders 对Messages
由编译器自动生成的message类是不可变的,一旦一个message对象构建以后,就象java中的String类一样是不可变的。创建一个message时,必须首先创建一个builder,设置必须的一些值后,再调用builder的build()方法。
也许你已经注意到了,builder的每个方法在消息修改后又会返回builder,这个返回对象又可以调用其它方法。这种方式对于在同一行操作不同的方法提供了便利。如下的代码示例,创建一个Person实例。
Person john = Person.newBuilder() .setId(1234) .setName("John Doe") .setEmail("jdoe@example.com") .addPhone( Person.PhoneNumber.newBuilder() .setNumber("555-4321") .setType(Person.PhoneType.HOME)) .build();
|
l 标准的Message方法
对于每个message或builder类也包含一些方法用于检查或操作整个消息,如:
· isInitialized()
:检查是否所有的required字段已经设置了值;
· toString()
:返回一个易于阅读的消息结果,对于调试来说非常有用;
· mergeFrom(Message other)
: 将其它内部merger到当前的消息中,重写单一值域或者新增repeated域,仅用于builder。
· clear()
:将所有域清空设置,仅用于builder。
l 解析及序列化
最终,protocol buffer类就可以通过一些方法来完成消息的读写入及读取。如:
· byte[] toByteArray()
:
消息序列化并返回一个字节数组;
· static Person parseFrom(byte[] data)
:
从一个特定的字节数组解析成消息;
· void writeTo(OutputStream output)
:
序列化消息并将其写入到OutputStream中;
· static Person parseFrom(InputStreaminput)
:
从InputStream流中读取并解析消息。
上述提供的仅仅是解析及序列化的一组接口,可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/java/com/google/protobuf/Message.html中查阅更全面的的接口。
l 写入消息
接下来先看如何来用protocol buffer类,对于地址薄应用首先需要将个人资料写入地址薄中。为了做到这些,需要创建protocol buffer类并将信息写入。程序设计如下,会先从一个文件读取AddressBook信息,通过用户手工输入一个Person的信息,交将其回写至AddressBook文件中。代码示例如下,其中高亮部分是protobuf自动生成的代码。
import com.example.tutorial.AddressBookProtos.AddressBook; import com.example.tutorial.AddressBookProtos.Person; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.PrintStream;
class AddPerson { // This function fills in a Person message based on user input. static Person PromptForAddress(BufferedReader stdin, PrintStream stdout) throws IOException { Person.Builder person = Person.newBuilder();
stdout.print("Enter person ID: "); person.setId(Integer.valueOf(stdin.readLine()));
stdout.print("Enter name: "); person.setName(stdin.readLine());
stdout.print("Enter email address (blank for none): "); String email = stdin.readLine(); if (email.length() > 0) { person.setEmail(email); }
while (true) { stdout.print("Enter a phone number (or leave blank to finish): "); String number = stdin.readLine(); if (number.length() == 0) { break; }
Person.PhoneNumber.Builder phoneNumber = Person.PhoneNumber.newBuilder().setNumber(number);
stdout.print("Is this a mobile, home, or work phone? "); String type = stdin.readLine(); if (type.equals("mobile")) { phoneNumber.setType(Person.PhoneType.MOBILE); } else if (type.equals("home")) { phoneNumber.setType(Person.PhoneType.HOME); } else if (type.equals("work")) { phoneNumber.setType(Person.PhoneType.WORK); } else { stdout.println("Unknown phone type. Using default."); }
person.addPhone(phoneNumber); }
return person.build(); }
// Main function: Reads the entire address book from a file, // adds one person based on user input, then writes it back out to the same // file. public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: AddPerson ADDRESS_BOOK_FILE"); System.exit(-1); }
AddressBook.Builder addressBook = AddressBook.newBuilder();
// Read the existing address book. try { addressBook.mergeFrom(new FileInputStream(args[0])); } catch (FileNotFoundException e) { System.out.println(args[0] + ": File not found. Creating a new file."); }
// Add an address. addressBook.addPerson( PromptForAddress(new BufferedReader(new InputStreamReader(System.in)), System.out));
// Write the new address book back to disk. FileOutputStream output = new FileOutputStream(args[0]); addressBook.build().writeTo(output); output.close(); } }
|
l 读取消息
当然了,如果只有地址薄不能读取也是一件悲剧的事情,下面的代码示例就是从文件中读取该地址薄中的个人详细信息。
import com.example.tutorial.AddressBookProtos.AddressBook; import com.example.tutorial.AddressBookProtos.Person; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintStream;
class ListPeople { // Iterates though all people in the AddressBook and prints info about them. static void Print(AddressBook addressBook) { for (Person person: addressBook.getPersonList()) { System.out.println("Person ID: " + person.getId()); System.out.println(" Name: " + person.getName()); if (person.hasEmail()) { System.out.println(" E-mail address: " + person.getEmail()); }
for (Person.PhoneNumber phoneNumber : person.getPhoneList()) { switch (phoneNumber.getType()) { case MOBILE: System.out.print(" Mobile phone #: "); break; case HOME: System.out.print(" Home phone #: "); break; case WORK: System.out.print(" Work phone #: "); break; } System.out.println(phoneNumber.getNumber()); } } }
// Main function: Reads the entire address book from a file and prints all // the information inside. public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: ListPeople ADDRESS_BOOK_FILE"); System.exit(-1); }
// Read the existing address book. AddressBook addressBook = AddressBook.parseFrom(new FileInputStream(args[0]));
Print(addressBook); } }
|
l 对Protocol Buffer进行扩展
有时会发现在发布完protocolbuffer代码后,需要对其进行扩展升级。如果想让新代码向后兼容,而且老代码能够向前兼容,此时需要遵循以下的规则。
· 不能改变已存在域的标识号;
· 不要任意添加或删除required修饰的域;
· 可以删除optional或repeated修饰的域;
· 可以新增optional或repeated修饰的域,但是必须使用新的标识号。
如果按照上述规约进行了升级,旧的代码将可以读取新的消息并将一些新的字段忽略掉。对于旧代码,被删除的optional域将会使用其默认值,删除的repeated域将会被置空。新代码中也将能够透明地读取旧的消息,但是有一点需要明确,那就是新的optional域不能出现在旧消息中,可以通过has方法进行明确检查,或者在.proto文件中为该字段提供一个默认值。如果一个optional元素没有明确的声明默认值的话,则会根据其类型取默认值,如:字符串类型,取空串为默认值;布尔类型取false为其默认值;数字类型取0为其默认值。如果新增了一个repeated域,新代码将不能判断其是否是空,老代码也不会设置其值,且它并没有has方法。
l 高级用法
Protocol Buffers目前已经能够提供的功能远超过了上述介绍的简单访问及序列化,可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/java/index.html中发掘更高级的特性。
Protocol消息类提供的一个主要特性是反射,对于任何具体的消息类型在不需要写代码的情况下就可以迭代其中的域并操控其中的值。其有效的应用场景即可将其它编码(XML、JSON)的消息转换成protocol消息。一个更高级的反射应用即可以发现同一类型消息的差异,或者是采用一系列正则表达式来匹配一定的消息内容。充分发挥想象力,protocol buffer将能够解决更广范围的问题。其中反射是作为Message及Message.Builder的接口的一部分而提供的。
分享到:
相关推荐
Java protobuf框架使用向导ProtoBuf,全称是Protocol Buffers, 它是谷歌内部用的一种高效的、可扩展的对结构化数据进行编码的格式规范。谷歌自己内部很多程序之间的通信协议都用了ProtoBuf
约会NIO高效并发框架——Netty,需要用到Java的基础知识(多线程,网络编程,IO,设计模式尤其是代理模式),介绍了Netty的高级架构设计和核心模块组件,Google上的Protobuf作为编码解码的数据存储格式,Netty编码器...
ProtoBuf,全称是Protocol Buffers, 它是中软卓越内部用的一种高效的、可扩展的对结构化数据进行编码的格式规范。中软卓越自己内部很多程序之间的通信协议都用了ProtoBuf。该文档主要介绍了Java_protobuf框架的使用...
编码csv到protobuf npm install csv-protobuf-stream 用法 var csvProtobuf = require ( 'csv-protobuf-stream' ) ; var split = require ( 'binary-split' ) ; var encoder = csvProtobuf ( ) ; fs . ...
Vert.x-ProtoBuf 使用VertX测试协议缓冲区[JAVA] Vert.x是用于在JVM上构建响应式应用程序的工具包。 协议缓冲区是一种与语言无关,与平台无关的可扩展机制,用于序列化结构化数据。 该示例部署了两个顶点,它们...
spring-boot-serializations-producer:负责产生的Protobuf和json编码的数据。 默认情况下,该项目从端口8080开始,并公开2个终结点:一个公开JSON,一个公开Protobuf。 spring-boot-serializations-consumer:负责...
【作品名称】:基于Unity3d和Java的聊天室demo,主要功能是实现一个完整的c/s架构,包括编码,解码,消息广播 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、...
Java-Redisson-ProtoBufCodec
通过Avro将Protobuf转换为Parquet为什么? 本示例说明如何使用Parquet的Avro对象模型和Avro对protobuf对象的支持将Protobuf文件转换为Parquet文件。 Parquet具有直接与Protobuf对象一起使用的模块,但是在为其他读取...
[1]性能优势在很大程度上取决于用例和消息格式,但与Protobuf-Java 3.9.1 ,大多数常见用例在编码和解码速度方面的性能提升大约是 2 倍。 有关更多信息,请参阅 部分。 运行时库 您可以在 Maven Central 上的以下...
此步骤允许从使用 Google Protocol Buffers 编码的对象访问独立字段。 截图 以下是通过 Apache Kafka 传输的 Protocol Buffers 消息的实时解码示例: 安装 从下载pentaho-protobuf-decode Zip 存档。 将下载的存档...
有限的兼容protobuf的语法规则 和 编码规则。包括protobuf2.0和3.0的编码规则。 实现proto结构的序列化、反序列化功能。 代码生成工具,需要boost库支持(主要用了spirit库做文本解析),本人用的是boost.1.64.0。 ...
有限的兼容protobuf的语法规则 和 编码规则。包括protobuf2.0和3.0的编码规则。 实现proto结构的序列化、反序列化功能。 代码生成工具,需要boost库支持(主要用了spirit库做文本解析),本人用的是boost.1.64.0。 ...
47_Netty服务器与客户端编码模式回顾及源码分析准备 48_Netty与NIO系统总结及NIO与Netty之间的关联关系分析 49_零拷贝深入剖析及用户空间与内核空间切换方式 50_零拷贝实例深度剖析 51_NIO零拷贝彻底分析与Gather...
净值 jdk bio,nio,aio各种使用案例,深入理解netty,结合源码以及文章分析: jdk原生nio的缓冲区使用 ...使用netty自带的编码解码器编写一个聊天室 在9的基础上加入私聊功能 在10的基础上加入pr
锐捷java笔试题 软件复用——谈复用解决方案 长连接心跳机制 ######使用netty来达到实现Client与Server的长连接通讯和心跳检测的目的。(JDK1.8、netty5) 基本思路:netty服务端通过一个Map保存所有连接上来的...
Protobuf解编码框架 Prostostuff:它基于Protobuf序列化框架,面向POJO,无需编写.proto文件。 Quartz时间调度 Redis Cluster集群高可用方案(未整合) RPC(远程过程调用) ZooKeeper(未实现):提供服务注册与发现功能...
基础篇走进Java NIO 第1章 Java的I/O演进之路 第2章 NIO入门 第3章 Netty入门应用 第4章 TCP粘包/拆包问题解析之道 第5章 分隔符和定长解析码器的应用 第6章 编码技术 第7章 MessagePack 编解码 第8章 Google ...
格式化您的C系列代码 该插件使用以特定的编码样式格式化代码。 当前支持以下语言: C C ++ 目标C JavaScript Java打字稿原虫屏幕截图要求clang-format命令( 3.4或更高版本),捆绑在Clang附加工具中 (强烈推荐) ...
Hbase是Apache的NoSQL分布式可扩展Hadoop...Thrift网关和支持XML,Protobuf和二进制数据编码选项的REST-ful Web服务 可扩展的基于Jruby的(JIRB)Shell 支持通过Hadoop指标子系统将指标导出到文件或Ganglia;或通过JMX