0 前言
在 JAVA 团队外部交互数据时,有几种罕用的办法,比方 JAVA 原生序列化办法、Json 序列化办法、Protobuf 序列化办法等,比照试验详见:几种序列化形式的性能比拟。
从比照试验可知 protobuf 性能最佳,因为其高效的序列化性能以及优良的跨平台能力,被广泛应用于跨平台之间的数据交互。但在不须要思考跨平台的数据交互时,protobuf 的繁琐流程成了烦人的“污点”。
基于 ptoro 传输数据的个别流程是,先编写 proto 文件,再手动编译 proto 文件生成 java 类,而后实例化 java 类并填充数据,最初编译成二进制数据进行传输。其中编写及手动编译 proto 文件是个较繁琐的过程(比方简单我的项目中 proto 之间的互相援用),而且由 proto 生成的 java 类应用的是创建者模式(即 Bulider 模式),其实例化后的赋值过程与我的项目历史代码格调不统一,为带来较大的改变。
为解决这个问题,本文将介绍百度团队开发的 Jprotobuf 工具,并总结应用过程中的注意事项,以及通过试验验证其与谷歌团队开发的 Protobuf 性能的一致性。
1 Jprotobuf 介绍
1.1 定义
jprotobuf 是针对 JAVA 程序开发一套繁难类库,目标是简化 JAVA 语言对 protobuf 类库的应用。应用 jprotobuf 能够无需再去理解 proto 文件操作与语法,间接应用 JAVA 注解定义字段类型即可。
github 地址:https://github.com/jhunters/j…
1.2 原理
jprotobuf 工作原理如下:
- 扫描类上的注解的信息,进行剖析(与 protobuf 读取 proto 文件进行剖析过程类似)
- 依据注解剖析的后果,动静生成 java 代码进行 protobuf 序列化与反序列化的性能实现
- 应用 JDK6 及以上的 code compile API 进行编译后加载到 classloader
1.3 性能
jprotobuf 次要性能耗费在扫描类上注解,动静生成代码编译的过程。在执行序列化与反序列化的过程中,简直与 protobuf 生成的代码效率等同。如果应用预编译插件,则无需在运行中进行代码生成与编译,效率更高。
1.4 特点
- 无需编写 proto 文件及繁琐的手工编译过程,反对基于 POJO 对象的注解形式,方便快捷。
反对 protobuf 所有类型,包含对象嵌套,数组,枚举类型 - 提供依据 proto 文件,动静生成代理对象,可省去 POJO 对象的编写工作。
残缺反对 proto 文件所有性能,包含内联对象,匿名对象,枚举类型 - 提供从 POJO 对象的注解形式主动生成 proto 文件的性能,不便 proto 形容文件的治理与保护
- 提供预编译 Maven 插件,进一步晋升运行性能
- 新增预编译 gradle 插件
- 2.x 版本。反对 TimeStamp 类型, 与原生 protobuf 保持一致。反对 Date 类型,应用 long 类型传递 docs
2 Jprotobuf 装置
maven 代码如下:
<!-- jprotobuf -->
<dependency>
<groupId>com.baidu</groupId>
<artifactId>jprotobuf</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>com.baidu</groupId>
<artifactId>jprotobuf-precompile-plugin</artifactId>
<version>2.2.2</version>
</dependency>
3 案例数据
测试思路:通过各自的办法构建雷同的测试数据,进行序列化和反序列比拟两者之间的差别。
注意事项:设计的案例数据尽量蕴含所有的数据类型。
3.1 Protobuf 数据定义
组织构造如图,其中 data 依赖 student,student 依赖 person,teacher 作为第三方扩大包,用于测试扩大性能。
data.proto 代码如下:
// Google Protocol Buffers Version 3.
syntax = "proto3";
// Package name.
package prcticeProto.messages;
// Options for code generation.
option java_package = "learnProto.practiceTest.protoModel";
option java_outer_classname = "SchoolModel";
option java_multiple_files = true;
// import packages
import "google/protobuf/any.proto";
import "practiceProto/categories/student.proto";
message School {
message Location{
string name=1;
uint32 id=2;
}
Location schoolLocation = 1;
bool isOpen =2;
repeated categories.Student allStudents = 3;
google.protobuf.Any extend =4;
}
student.proto 代码如下:
// Google Protocol Buffers Version 3.
syntax = "proto3";
// Package name.
package prcticeProto.categories;
// Options for code generation.
option java_package = "learnProto.practiceTest.protoModel";
option java_outer_classname = "StudentModel";
option java_multiple_files = true;
// import packages
import "practiceProto/base/person.proto";
message Student {
base.Person baseInfo = 1;
fixed32 calssId = 2;
sint32 score = 3;
}
person.proto 代码如下:
// Google Protocol Buffers Version 3.
syntax = "proto3";
// Package name.
package prcticeProto.base;
// Options for code generation.
option java_package = "learnProto.practiceTest.protoModel";
option java_outer_classname = "PersonModel";
option java_multiple_files = true;
message Person{
message Location{
string placeName=1;
uint64 placeId=2;
}
enum Gender{
man=0;
woman=1;
}
string name = 1;
int32 age=2;
Gender gender=3;
float height=4;
double weight=5;
Location location=6;
}
teacher.proto 代码如下:
// Google Protocol Buffers Version 3.
syntax = "proto3";
// Options for code generation.
option java_package = "learnProto.practiceTest.protoModel";
option java_outer_classname = "TeacherModel";
option java_multiple_files = true;
message Teacher{
string name = 1;
int32 age=2;
}
3.2 Jprotobuf 数据定义
组织构造如图所示。
Jprotobuf 的定义语法详见:https://github.com/jhunters/j…
school 代码如下:
package learnProto.jprotobuf.model;
import com.baidu.bjf.remoting.protobuf.Any;
import com.baidu.bjf.remoting.protobuf.FieldType;
import com.baidu.bjf.remoting.protobuf.annotation.Protobuf;
import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;
import java.util.List;
@ProtobufClass
public class School {
@ProtobufClass
public static class Location{@Protobuf(fieldType= FieldType.STRING, order=1)
private String name;
@Protobuf(fieldType= FieldType.UINT32, order=2)
private Integer id;
public String getName() {return name;}
public Location setName(String name) {
this.name = name;
return this;
}
public Integer getId() {return id;}
public Location setId(Integer id) {
this.id = id;
return this;
}
}
@Protobuf(fieldType= FieldType.OBJECT, order=1)
private Location schoolLocation;
@Protobuf(fieldType= FieldType.BOOL, order=2)
private Boolean isOpen;
@Protobuf(fieldType= FieldType.OBJECT, order=3)
private List<Student> allStudents;
@Protobuf(fieldType = FieldType.OBJECT,order = 4)
private Any extend;
public Any getExtend() {return extend;}
public School setExtend(Any extend) {
this.extend = extend;
return this;
}
public Location getSchoolLocation() {return schoolLocation;}
public School setSchoolLocation(Location schoolLocation) {
this.schoolLocation = schoolLocation;
return this;
}
public Boolean getOpen() {return isOpen;}
public School setOpen(Boolean open) {
isOpen = open;
return this;
}
public List<Student> getAllStudents() {return allStudents;}
public School setAllStudents(List<Student> allStudents) {
this.allStudents = allStudents;
return this;
}
}
student 代码如下:
package learnProto.jprotobuf.model;
import com.baidu.bjf.remoting.protobuf.FieldType;
import com.baidu.bjf.remoting.protobuf.annotation.Protobuf;
import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;
@ProtobufClass
public class Student{@Protobuf(fieldType=FieldType.OBJECT, order=1)
private Person baseInfo;
@Protobuf(fieldType=FieldType.FIXED32, order=2)
private Integer calssId;
@Protobuf(fieldType=FieldType.SINT32, order=3)
private Integer score;
public Person getBaseInfo() {return baseInfo;}
public Student setBaseInfo(Person baseInfo) {
this.baseInfo = baseInfo;
return this;
}
public Integer getCalssId() {return calssId;}
public Student setCalssId(Integer calssId) {
this.calssId = calssId;
return this;
}
public Integer getScore() {return score;}
public Student setScore(Integer score) {
this.score = score;
return this;
}
}
person 代码如下:
package learnProto.jprotobuf.model;
import com.baidu.bjf.remoting.protobuf.FieldType;
import com.baidu.bjf.remoting.protobuf.annotation.Protobuf;
import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;
@ProtobufClass
public class Person {
@ProtobufClass
public enum Gender {MAN(0),WOMAN(1);
@Protobuf(fieldType= FieldType.INT32, order=1)
private final Integer value;
Gender(Integer value) {this.value = value;}
public Integer toValue() { return this.value;}
public Integer value() {return toValue();
}
}
@ProtobufClass
public static class Location{@Protobuf(fieldType= FieldType.STRING, order=1)
private String placeName;
@Protobuf(fieldType= FieldType.UINT64, order=2)
private Long placeId;
public String getPlaceName() {return placeName;}
public Location setPlaceName(String placeName) {
this.placeName = placeName;
return this;
}
public Long getPlaceId() {return placeId;}
public Location setPlaceId(Long placeId) {
this.placeId = placeId;
return this;
}
}
@Protobuf(fieldType= FieldType.STRING, order=1)
private String name;
@Protobuf(fieldType= FieldType.INT32, order=2)
private Integer age;
@Protobuf(fieldType= FieldType.ENUM, order=3)
private Gender gender;
@Protobuf(fieldType= FieldType.FLOAT, order=4)
private Float height;
@Protobuf(fieldType= FieldType.DOUBLE, order=5)
private Double weight;
@Protobuf(fieldType= FieldType.OBJECT, order=6)
private Location personLocation;
public String getName() {return name;}
public Person setName(String name) {
this.name = name;
return this;
}
public Integer getAge() {return age;}
public Person setAge(Integer age) {
this.age = age;
return this;
}
public Gender getGender() {return gender;}
public Person setGender(Gender gender) {
this.gender = gender;
return this;
}
public Float getHeight() {return height;}
public Person setHeight(Float height) {
this.height = height;
return this;
}
public Double getWeight() {return weight;}
public Person setWeight(Double weight) {
this.weight = weight;
return this;
}
public Location getPersonLocation() {return personLocation;}
public Person setPersonLocation(Location personLocation) {
this.personLocation = personLocation;
return this;
}
}
teacher 代码如下:
package learnProto.jprotobuf.model;
import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;
@ProtobufClass
public class Teacher {
private Integer id;
private String name;
public Integer getId() {return id;}
public Teacher setId(Integer id) {
this.id = id;
return this;
}
public String getName() {return name;}
public Teacher setName(String name) {
this.name = name;
return this;
}
}
4 案例测试
4.1 根本数据类型测试
public class jprotobufTest {public static void writeFileByte(byte[] b,String path) {File file = new File(path);
long size = file.length();
try (FileOutputStream fos = new FileOutputStream(file)) {fos.write(b);
fos.flush();} catch (IOException e) {e.printStackTrace();
}
}
public static byte[] readFileByte(String path) {File file = new File(path);
long size = file.length();
byte[] b = new byte[(int) size];
try (InputStream fis = new FileInputStream(file)) {if (fis.read(b) < 0) {return null;}
} catch (IOException e) {e.printStackTrace();
}
return b;
}
public void protobuff(String path){
// 实例化
learnProto.practiceTest.protoModel.Person.Builder builderPerson = learnProto.practiceTest.protoModel.Person.newBuilder().setAge(10).setGender(learnProto.practiceTest.protoModel.Person.Gender.woman).setName("Tom").setHeight(100.00f).setWeight(100.00d).setLocation(learnProto.practiceTest.protoModel.Person.Location.newBuilder().setPlaceId(123l).setPlaceName("hubei")
);
learnProto.practiceTest.protoModel.School school = learnProto.practiceTest.protoModel.School.newBuilder()
.setIsOpen(true)
.setSchoolLocation(learnProto.practiceTest.protoModel.School.Location.newBuilder().setId(123).setName("hubei"))
.addAllStudents(Student.newBuilder().setBaseInfo(builderPerson).setCalssId(10).setScore(-1))
.addAllStudents(Student.newBuilder().setBaseInfo(builderPerson).setCalssId(10).setScore(1))
.build();
try {
// 序列化
byte[] bytes = school.toByteArray();
writeFileByte(bytes,path);
System.out.println("protobuf 序列化后的数据:" + Arrays.toString(bytes)+", 字节个数:"+bytes.length);
// 反序列化
byte[] bytes1 = readFileByte(path);
learnProto.practiceTest.protoModel.School parseFrom = learnProto.practiceTest.protoModel.School.parseFrom(bytes1);
} catch (InvalidProtocolBufferException e) {e.printStackTrace();
}
}
public void jprotobuf(String path){
// 实例化
Person person = new Person().setAge(10).setGender(Person.Gender.MAN).setName("Tom").setHeight(100.00f).setWeight(100.00d).setPersonLocation(new Person.Location().setPlaceId(123l).setPlaceName("hubei")
);
ArrayList<learnProto.jprotobuf.model.Student> studentArrayList = new ArrayList<>();
studentArrayList.add(new learnProto.jprotobuf.model.Student().setCalssId(10).setScore(-1).setBaseInfo(person));
studentArrayList.add(new learnProto.jprotobuf.model.Student().setCalssId(10).setScore(1).setBaseInfo(person));
School sch = new School().setOpen(true).setAllStudents(studentArrayList).setSchoolLocation(new School.Location().setId(123).setName("hubei"));
try {
// 序列化
Codec<School> simpleTypeCodec = ProtobufProxy.create(School.class);
byte[] b = simpleTypeCodec.encode(sch);
writeFileByte(b,path);
System.out.println("Jprotobuf 序列化后的数据:" + Arrays.toString(b)+", 字节个数:"+b.length);
// 反序列化
byte[] bytes =readFileByte(path);
School newStt = simpleTypeCodec.decode(bytes);
} catch (IOException e) {e.printStackTrace();
}
}
@Test
public void test(){protobuff("C:UsersadminDesktopProtodemo.pack");
jprotobuf("C:UsersadminDesktopJprotodemo.pack");
}
}
运行后果:
protobuf 序列化后的数据:[10, 9, 10, 5, 104, 117, 98, 101, 105, 16, 123, 16, 1, 26, 43, 10, 34, 10, 3, 84, 111, 109, 16, 10, 24, 1, 37, 0, 0, -56, 66, 41, 0, 0, 0, 0, 0, 0, 89, 64, 50, 9, 10, 5, 104, 117, 98, 101, 105, 16, 123, 21, 10, 0, 0, 0, 24, 1, 26, 43, 10, 34, 10, 3, 84, 111, 109, 16, 10, 24, 1, 37, 0, 0, -56, 66, 41, 0, 0, 0, 0, 0, 0, 89, 64, 50, 9, 10, 5, 104, 117, 98, 101, 105, 16, 123, 21, 10, 0, 0, 0, 24, 2], 字节个数:103
Jprotobuf 序列化后的数据:[10, 9, 10, 5, 104, 117, 98, 101, 105, 16, 123, 16, 1, 26, 43, 10, 34, 10, 3, 84, 111, 109, 16, 10, 24, 0, 37, 0, 0, -56, 66, 41, 0, 0, 0, 0, 0, 0, 89, 64, 50, 9, 10, 5, 104, 117, 98, 101, 105, 16, 123, 21, 10, 0, 0, 0, 24, 1, 26, 43, 10, 34, 10, 3, 84, 111, 109, 16, 10, 24, 0, 37, 0, 0, -56, 66, 41, 0, 0, 0, 0, 0, 0, 89, 64, 50, 9, 10, 5, 104, 117, 98, 101, 105, 16, 123, 21, 10, 0, 0, 0, 24, 2], 字节个数:103
试验表明,Jprotobuf 的序列化办法与 Protobuf 统一,能够放心使用。
4.2 第三方扩大测试
@Test
public void schooltest() throws IOException {
// jproto
Any any = Any.pack(new Teacher());
School person = new School().setExtend(any);
Codec<School> simpleTypeCodec = ProtobufProxy.create(School.class);
try {
// 序列化
byte[] b = simpleTypeCodec.encode(person);
writeFileByte(b,"C:UsersadminDesktopjproto.pack");
System.out.println("jproto 序列化后的数据:" + Arrays.toString(b)+", 字节个数:"+b.length);
// 反序列化
byte[] bytes =readFileByte("C:UsersadminDesktopjproto.pack");
School newStt = simpleTypeCodec.decode(bytes);
} catch (IOException e) {e.printStackTrace();
}
// proto
com.google.protobuf.Any any1 = com.google.protobuf.Any.pack(learnProto.practiceTest.protoModel.Teacher.newBuilder().build());
learnProto.practiceTest.protoModel.School builderPerson = learnProto.practiceTest.protoModel.School.newBuilder()
.setExtend(any1)
.build();
try {
// 序列化
String path="C:UsersadminDesktopProtodemo.pack";
byte[] bytes = builderPerson.toByteArray();
writeFileByte(bytes,path);
System.out.println("protobuf 序列化后的数据:" + Arrays.toString(bytes)+", 字节个数:"+bytes.length);
// 反序列化
byte[] bytes1 = readFileByte(path);
learnProto.practiceTest.protoModel.School parseFrom = learnProto.practiceTest.protoModel.School.parseFrom(bytes1);
} catch (InvalidProtocolBufferException e) {e.printStackTrace();
}
}
运行后果:
jproto 序列化后的数据:[34, 58, 10, 54, 116, 121, 112, 101, 46, 103, 111, 111, 103, 108, 101, 97, 112, 105, 115, 46, 99, 111, 109, 47, 108, 101, 97, 114, 110, 80, 114, 111, 116, 111, 46, 106, 112, 114, 111, 116, 111, 98, 117, 102, 46, 109, 111, 100, 101, 108, 46, 84, 101, 97, 99, 104, 101, 114, 18, 0], 字节个数:60
protobuf 序列化后的数据:[34, 29, 10, 27, 116, 121, 112, 101, 46, 103, 111, 111, 103, 108, 101, 97, 112, 105, 115, 46, 99, 111, 109, 47, 84, 101, 97, 99, 104, 101, 114], 字节个数:31
试验表明,Jprotobuf 的扩大性能能够失常应用,但其机制可能与 protobuf 有差别。
5 应用总结
1、字段的注解 @Protobuf,能够不写,不写的状况下依照默认类型,order 依照书写的程序;也能够写,自定义类型和 order。但 肯定不要有的字段写,有的字段不写,order 会乱!
2、构建的 @ProtobufClass 不要带有有参结构器,实例化时也不能带参数结构。只能无参结构后,通过 set 或 add 赋值(与 proto 的实例化标准统一)。
3、能够调用_getIDL_取得 IDL,即 proto 文件,但所有的嵌套构造都被拆开成了独自的构造。
4、setter 的返回类型能够设定为类自身,从而实现链式。如图:
5、定义根本数据类型的数据时,要用包装类。比方对于整型,不要写 int,而应该写 INTGER,如上图中的 id,如果设置为 int,零碎默认初始化了 id=0,即便没有赋值,该数值在序列化时会被写入数据;如果设置为 INTGER,没有赋值,不会被写入序列化的字节序中。(此处与 proto 不同,proto 中字段设定值等于默认值时,序列化时数据不会被写入,反序列化后所有没被赋值的字段都被赋对应类型的默认值;而 jprotobuf 齐全依照用户输出的志愿,序列化之前赋值什么,序列化时就有什么,反序列化时也对应有什么,如果字段没被赋值,反序列化后为 null,enum 为第一个枚举值)。
6、反序列化应用时留神:没被赋值的字段反序列化后值为 null,并非默认值。
7、设定了多个字段,有些没有赋值,并不会影响数据序列化后的大小。
6 参考文献
[1] 几种序列化形式的性能比拟
[2] https://github.com/jhunters/j…
[3] https://github.com/jhunters/j…