1. 概述
在系统中,Protocol Buffer 的使用有两种方式:1)定义 .proto 文件,生成相应的 Bean 对象及序列化的代码;2)根据现有的 Java Bean 对象,动态生成对应的 schema, 使用工具类完成对象的序列化。这两种方式的优缺点也比较明显,.proto 文件支持向前、向后兼容,后者使用简单,引用工具类,便可支持序列化操作,缺点是存在代码兼容性问题。
说明:这篇文章只是简单讲述两种方式的使用,详细的内容请参考官方文档。
2. 定义 proto 文件
2.1 安装部署 Protocol Buffer 编译器
以 windows 环境为例,从官方下载 Protocol Buffer,如 protoc-3.18.1-win64.zip, 解压缩到指定目录,并将 bin 目录加到 Path 环境变量中即可。
1 | SET PROTOC_HOME=D:\app\protoc-3.18.1-win64 |
下载地址:
https://github.com/protocolbuffers/protobuf/releases/tag/v3.18.1
2.2 定义 proto 文件
在官方的 addressbook.proto 文件基础上,添加了 map 对象的变量 people_map,在真实场景中,该对象可以通过 peoples 列表遍历得到,不需要传递 map 对象,否则会传输两份数据,在这里仅仅是演示作用。
1 | // [START declaration] |
说明:
- syntax: PB 版本,有 v2 和 v3 两个版本,值分别为 proto2 和 proto3;
- package: 包名,表示命名空间,用于解决名称冲突,在 Java 中转换为 package 包名;
- java_multiple_files:Java 申明,若为 true, 表示为每一个 message 生成独立的文件(嵌套的 message 除外);
- java_package:Java 申明,指定 Java 的 package 名称,它为覆盖 package 属性指定的包名;
- java_outer_classname:Java 申明,指定输出的 Java 类名;
- import:引入外部的 proto 文件;
- message:定义一个消息,对应 Java 中的对象,message 可以嵌套定义;
- enum:枚举对象,下标从 0 开始;
- repeated:表示 0 或 n 个对象,可以定义数组对象;
- map:map 对象;
- optional:描述符,表示该字段为可选字段,如果没有设置的话,使用默认值,在 proto3 中已经取消 required 字段;
- tag:如同 =1,字段在 message 中的惟一标示,在二进制编码中使用 tag 代表该字段,1~15 使用一个字节表示,处于优化的目的,常用的字段应该放在这个区域。
map 对象等同于如下的定义,它实际上就是一个包含 key 和 value 对象的数组。1
2
3
4
5
6message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
2.3 编译 proto 文件
1 | protoc -I=. --java_out=. addressbookV2.proto |
说明:
- -I:指定 proto 文件所在的源目录;
- –java_out:指定 Java 语言编译输出的目录;
生成的代码如下所示:1
2
3
4
5
6
7
8
9└─org
└─noahsark
└─tutorial
└─protos
AddressBook.java
AddressBookOrBuilder.java
AddressBookProtos.java
Person.java
PersonOrBuilder.java
2.4 运行代码
将生成的的代码导入工程,引入 Protocol Buffer jar 包。1
2
3
4
5<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.18.1</version>
</dependency>
构造 AddressBook 对象并序列化数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23private static byte [] marshal() {
// 构造 Person 对象
Person john =
Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("jdoe@example.com")
.addPhones(
Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(Person.PhoneType.HOME))
.build();
// 构造 AddressBook 对象
AddressBook addressBook = AddressBook.newBuilder()
.addPeoples(john)
.putPeopleMap(john.getName(),john).build();
// 序列化 AddressBook 对象
byte [] message = addressBook.toByteArray();
return message;
}
反序列化 AddressBook 对象1
2
3
4
5private static AddressBook unmarshal(byte [] message) throws InvalidProtocolBufferException {
AddressBook addressBook = AddressBook.parseFrom(message);
return addressBook;
}
输出 AddressBook 对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23private static void listAddressBook(AddressBook addressBook) {
for (Person person: addressBook.getPeoplesList()) {
System.out.println("Person ID: " + person.getId());
System.out.println(" Name: " + person.getName());
System.out.println(" E-mail address: " + person.getEmail());
for (Person.PhoneNumber phoneNumber : person.getPhonesList()) {
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());
}
}
}
使用 ProtocolBuffer 序列化及反序列化数据1
2
3
4
5
6
7
8
9
10
11
12public static void main(String[] args) throws InvalidProtocolBufferException {
// 构造 AddressBook 对象并序列化数据
byte [] message = marshal();
// 反序列化 AddressBook 对象
AddressBook addressBook = unmarshal(message);
// 输出 AddressBook 对象
listAddressBook(addressBook);
}
输出结果为:1
2
3
4Person ID: 1234
Name: John Doe
E-mail address: jdoe@example.com
Home phone #: 555-4321
3. 运行时模式
在该种方式下,不用定义 .proto 文件,引入 protostuff 工具包,然后使用其提供的 API 便可对 Java 对象进行 ProtocolBuffer 序列化/反序列化操作。
3.1 引入 protostuff
在 maven pom 文件中加入如下依赖:
1 | <!-- For the core formats (protostuff, protobuf, graph) --> |
3.2 定义 Java Bean 对象
1 | public class Person { |
3.3 定义工具类
1 | public class ProtostuffUtils { |
3.4 测试代码
1 | public static void main(String[] args) { |
输出结果为:1
2person:
Person{id=1, name='john', email='john@gmail.com', mobile='13845678912'}
4. 总结
这两种方式不同的使用场景,如果后期不考虑代码的兼容问题,可以使用 protostuff 工具包,简单方便,如果代码迭代更新比较多,需要考虑不同版本的兼容问题,那最好定义合适的 .proto 文件来保证代码的兼容性。
参考: