无需写.proto文件即可使用grpc序列化方法protobuf 的java工具-Jprotobuf

1,609 阅读5分钟

Jprotobuf,不用写.proto文件即可使用protobuf序列化的神器

protobuf-java 是google推出针对java语言版本的protobuf序列化sdk,也是grpc所采用的序列化方式,其耗时以及空间大小占用都优于大部分序列化,比如json,hessian。但是protobuf需要自己定义idl语句实现跨语言性,那有没有一个工具可以自动根据java类来生成idl的.proto文件呢?答案是有的,那就是Jprotobuf

文档:jprotobuf/Document.md at master · jhunters/jprotobuf (github.com)

Jprotobuf 使用

添加maven依赖

<!--截至2024-5-16 最新版本,推荐用最新版本,旧版本可能有未完善的问题-->
<dependency>
    <groupId>com.baidu</groupId>
    <artifactId>jprotobuf</artifactId>
    <version>2.4.22</version>
</dependency><!--预编译插件-->
<plugin>
    <groupId>com.baidu</groupId>
    <artifactId>jprotobuf-precompile-plugin</artifactId>
    <version>2.2.4</version>
    <configuration>
        <filterClassPackage>com.rpc.*</filterClassPackage>
        <generateProtoFile>true</generateProtoFile>
        <compileDependencies>true</compileDependencies>
    </configuration>
    <executions>
        <execution>
            <phase>compile</phase>
            <goals>
                <goal>precompile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

测试类,需要注意的是需要加上@ProtobufClass,以及无参构造和setter,getter

@Data
@ProtobufClass
@AllArgsConstructor
public class HeartBeat implements Serializable {
​
    private String initiator;
​
    Long updateTime;
​
    public HeartBeat(){
        this.updateTime = new Date().getTime();
    }
​
    public HeartBeat(String initiator){
        this.initiator = initiator;
        this.updateTime = new Date().getTime();
    }
}

测试代码

@Test
public void testSerialization() throws IOException {
    Codec<HeartBeat> heartBeatCodec = ProtobufProxy.create(HeartBeat.class);
    HeartBeat heartBeat = new HeartBeat("test");
    // 序列化
    byte[] encode = heartBeatCodec.encode(heartBeat);
    // 反序列化
    HeartBeat decode = heartBeatCodec.decode(encode);
    System.out.println(decode);
}

结果当然没有任何异常

HeartBeat(initiator=test, updateTime=1715844868208)

封装Jprotobuf序列化工具

@Component("protobufSerialization")
public class ProtobufSerialization implements RpcSerialization {
    private static final Codec<ProtobufWrapper> wrapperCodec = ProtobufProxy.create(ProtobufWrapper.class);
​
    public <T> byte[] serialize(T obj, Class<T> clz) throws IOException {
        // 内部有缓存,无需缓存
        Codec<T> codec = ProtobufProxy.create(clz);
        return codec.encode(obj);
    }
​
    public <T> byte[] serialize(ProtobufWrapper wrapper, Class<T> clz) throws IOException {
        // 如果是List类型,需要特殊处理
        Any any = Any.pack(wrapper);
        return wrapperCodec.encode(wrapper);
    }
​
​
    public <T> T deserialize(byte[] data, Class<T> clz) throws IOException {
        if(clz.isAssignableFrom(ProtobufWrapper.class)){
            return (T) wrapperCodec.decode(data);
        }
        Codec<T> codec = ProtobufProxy.create(clz);
        return codec.decode(data);
    }
}
​
@Data
@NoArgsConstructor
@AllArgsConstructor
@ProtobufClass
public class ProtobufWrapper {
    @Protobuf(fieldType = FieldType.OBJECT)
    private List<HeartBeat> data;
}

需要注意的是Jprotobuf无法直接对List进行序列化,需要使用类进行包装,同时你需要在list属性上使用@Protobuf(fieldType = FieldType.OBJECT)进行标注。并且List不可以使用泛型标识而需要固定的类。

ProtobufProxy.create 该方法是生成.proto文件方法,因此较为耗时,但作者也考虑到做了内部的缓存。所以自己就不必封装了。

Jprotobuf 工具类测试使用

@Test
public void testSerialization() throws IOException {
    ProtobufSerialization protobufSerialization = new ProtobufSerialization();
    HeartBeat heartBeat = new HeartBeat("test");
    // 序列化
    byte[] encode = protobufSerialization.serialize(heartBeat);
    // 反序列化
    HeartBeat decode =  protobufSerialization.deserialize(encode, HeartBeat.class);
    System.out.println(decode);
}

对象成功输出了。

HeartBeat(initiator=test, updateTime=1708679527671)
​
Process finished with exit code 0

工具性能测试,对比FastJson2以及hessian序列化方式

首先我们直接对一个对象进行一百万次的序列化以及反序列化测试一下

public void testSerialization() throws IOException {
    ProtobufSerialization protobufSerialization = new ProtobufSerialization();
    HeartBeat heartBeat = new HeartBeat("test");
    List<HeartBeat> heartBeats = new ArrayList<>();
    heartBeats.add(heartBeat);
    JsonSerialization jsonSerialization = new JsonSerialization();
    HessianSerialization hessianSerialization = new HessianSerialization();
    Long startTime = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        byte[] serialize = hessianSerialization.serialize(heartBeat, HeartBeat.class);
        HeartBeat deserialize = hessianSerialization.deserialize(null, serialize, HeartBeat.class);
    }
    log.info("hessianSerialization序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
    startTime = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        byte[] serialize = jsonSerialization.serialize(heartBeat, HeartBeat.class);
        HeartBeat deserialize = jsonSerialization.deserialize(null, serialize, HeartBeat.class);
    }
    log.info("jsonSerialization序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
    startTime = System.currentTimeMillis();
    ProtobufWrapper wrapper = new ProtobufWrapper(heartBeats);
    for (int i = 0; i < 1000000; i++) {
        byte[] serialize = protobufSerialization.serialize(heartBeat, HeartBeat.class);
        HeartBeat deserialize =  protobufSerialization.deserialize(null, serialize, HeartBeat.class);
    }
    log.info("protobuf序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
}

结果

16:11:20.565 [main] INFO Test1 - hessianSerialization序列化和反序列化耗时:2233
16:11:21.044 [main] INFO Test1 - jsonSerialization序列化和反序列化耗时:479
16:11:21.305 [main] INFO Test1 - protobuf序列化和反序列化耗时:261

看的出来protobuf序列化的速度不愧是遥遥领先~~,hessian确实是拉了啊,不过考虑到hessian序列化中保存了更多的类的信息,在一些不太吃性能的地方用起来还是非常方便的,大多数情况强转都能成功。

我们再来一次测试采用一个List数组,数组内部包含一个对象。这时protobuf序列化需要使用一个包装类来进行包装才可以序列化与反序列化

 @Test
    public void testSerialization() throws IOException {
        ProtobufSerialization protobufSerialization = new ProtobufSerialization();
        HeartBeat heartBeat = new HeartBeat("test");
        List<HeartBeat> heartBeats = new ArrayList<>();
        heartBeats.add(heartBeat);
        JsonSerialization jsonSerialization = new JsonSerialization();
        HessianSerialization hessianSerialization = new HessianSerialization();
        Long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            byte[] serialize = hessianSerialization.serialize(heartBeats, List.class);
            List deserialize = hessianSerialization.deserialize(null, serialize, List.class);
        }
        log.info("hessianSerialization序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            byte[] serialize = jsonSerialization.serialize(heartBeats, List.class);
            List deserialize = jsonSerialization.deserialize(SEND_HEARTBEAT, serialize, List.class);
        }
        log.info("jsonSerialization序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
        startTime = System.currentTimeMillis();
        ProtobufWrapper wrapper = new ProtobufWrapper(heartBeats);
        for (int i = 0; i < 1000000; i++) {
            byte[] serialize = protobufSerialization.serialize(wrapper, List.class);
            ProtobufWrapper deserialize = protobufSerialization.deserialize(null, serialize, ProtobufWrapper.class);
        }
        log.info("protobuf序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
    }

结果

16:07:57.388 [main] INFO Test1 - hessianSerialization序列化和反序列化耗时:2702
16:07:59.144 [main] INFO Test1 - jsonSerialization序列化和反序列化耗时:1756
16:08:00.552 [main] INFO Test1 - protobuf序列化和反序列化耗时:1408

这里可以看得出来protobuf与jsonSerialization差别并不大

于是我又测试了一千万次的次数

16:09:03.905 [main] INFO Test1 - hessianSerialization序列化和反序列化耗时:25866
16:09:13.974 [main] INFO Test1 - jsonSerialization序列化和反序列化耗时:10069
16:09:22.707 [main] INFO Test1 - protobuf序列化和反序列化耗时:8733

看的出来protobuf在处理List时效率本没有与fastJson差别太久。

写在最后

Jprotobuf是百度推出的适用于java代码的序列化工具类,虽然免去了写.proto文件的麻烦,但还是有很多限制性问题(大多也都是序列化本身的要求而不是工具类的功能缺失)比如无法直接对List,map等集合进行操作,刚开始ProtobufProxy.create耗时较高(可以使用预编译插件进行避免)无法对数组进行支持等问题,但还是很好一个工具。