乐趣区

关于protobuf:Protobuf专题四proto开发神器Jprotobuf

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 工作原理如下:

  1. 扫描类上的注解的信息,进行剖析(与 protobuf 读取 proto 文件进行剖析过程类似)
  2. 依据注解剖析的后果,动静生成 java 代码进行 protobuf 序列化与反序列化的性能实现
  3. 应用 JDK6 及以上的 code compile API 进行编译后加载到 classloader

1.3 性能

jprotobuf 次要性能耗费在扫描类上注解,动静生成代码编译的过程。在执行序列化与反序列化的过程中,简直与 protobuf 生成的代码效率等同。如果应用预编译插件,则无需在运行中进行代码生成与编译,效率更高。

1.4 特点

  1. 无需编写 proto 文件及繁琐的手工编译过程,反对基于 POJO 对象的注解形式,方便快捷。
    反对 protobuf 所有类型,包含对象嵌套,数组,枚举类型
  2. 提供依据 proto 文件,动静生成代理对象,可省去 POJO 对象的编写工作。
    残缺反对 proto 文件所有性能,包含内联对象,匿名对象,枚举类型
  3. 提供从 POJO 对象的注解形式主动生成 proto 文件的性能,不便 proto 形容文件的治理与保护
  4. 提供预编译 Maven 插件,进一步晋升运行性能
  5. 新增预编译 gradle 插件
  6. 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…

退出移动版