如何使用grpc的序列化方式-protobuf来序列化java的object对象?结尾有反转

如何使用protobuf来序列化object对象?结尾有反转

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 使用

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

java 复制代码
@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();
    }
}

测试代码

java 复制代码
@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);
    Assert.assertEquals(heartBeat, decode);
}

结果当然没有任何异常

log 复制代码
16:49:12.676 [main] DEBUG com.baidu.bjf.remoting.protobuf.ProtobufProxy - Field 'initiator' from com.rpc.domain.pojo.HeartBeat with @Protobuf annotation but not set order or order is 0, It will set order value to 1
16:49:12.682 [main] DEBUG com.baidu.bjf.remoting.protobuf.ProtobufProxy - Field 'updateTime' from com.rpc.domain.pojo.HeartBeat with @Protobuf annotation but not set order or order is 0, It will set order value to 2
16:49:12.797 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - Begin to compile source code: class is 'com.rpc.domain.pojo.HeartBeat$$JProtoBufClass'
16:49:14.395 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - compile source code done: class is 'com.rpc.domain.pojo.HeartBeat$$JProtoBufClass'
16:49:14.395 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - loading class 'com.rpc.domain.pojo.HeartBeat$$JProtoBufClass'
16:49:14.396 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - loading class done  'com.rpc.domain.pojo.HeartBeat$$JProtoBufClass'

Process finished with exit code 0

使用Jprotobuf来序列化Object会怎样

java 复制代码
@Test
public void testSerialization() throws IOException {
    Codec<Object> heartBeatCodec = ProtobufProxy.create(Object.class);
    HeartBeat heartBeat = new HeartBeat("test");
    // 序列化
    byte[] encode = heartBeatCodec.encode(heartBeat);
    // 反序列化
    HeartBeat decode = (HeartBeat) heartBeatCodec.decode(encode);
    Assert.assertEquals(heartBeat, decode);
}

结果

kotlin 复制代码
java.lang.IllegalArgumentException: Invalid class [java.lang.Object] no field use annotation @com.baidu.bjf.remoting.protobuf.annotation.Protobuf at class java.lang.Object

at com.baidu.bjf.remoting.protobuf.utils.ProtobufProxyUtils.fetchFieldInfos(ProtobufProxyUtils.java:121)
at com.baidu.bjf.remoting.protobuf.code.AbstractCodeGenerator.<init>(AbstractCodeGenerator.java:75)
at com.baidu.bjf.remoting.protobuf.code.TemplateCodeGenerator.<init>(TemplateCodeGenerator.java:87)
at com.baidu.bjf.remoting.protobuf.ProtobufProxy.getCodeGenerator(ProtobufProxy.java:389)
....

这里报错的原因是没有使用@ProtobufClass注解,但是根本原因是因为使用protobuf是需要严格定义字段类型,Jprotobuf也只是根据你的类的字段属性来自动生成类型,所以java的Object是对应不上idl中的类型的,所以无法解析。

那怎么才能实现序列化object呢

也是有解决办法的。在protobuf-java 3.4.3 版本后支持了Any类型,同时最新的Jprotobuf也支持Any了。

以下是使用Any类型来实现Object序列化,封装一个公共序列化-反序列化工具类

java 复制代码
public class ProtobufSerialization implements RpcSerialization {
    @Override
    public <T> byte[] serialize(T obj) throws IOException {
        Any any = Any.pack(obj);
        Codec<Any> anyCodec = ProtobufProxy.create(Any.class);
        return anyCodec.encode(any);
    }
    @Override
    public <T> T deserialize(byte[] data, Class<T> clz) throws IOException {
        Codec<Any> anyCodec = ProtobufProxy.create(Any.class);
        anyCodec.decode(data);
        Any any = anyCodec.decode(data);
        String clazzName = any.getCodecClass().split("/")[1];
        try {
            Class<?> clazz = Class.forName(clazzName);
            if (any.is(clazz)) {
                Object res = any.unpack(clazz);
                return (T) res;
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}

使用Any.pack 和unpack就可以完成对Object类型的封装,是不是非常简单方便呢。同时根据Any对象中包含的全类名我们就可以通过反射来获取字节码。

但是使用是有一定条件的,也就是你的Object必须加上@ProtobufClass,以及无参构造和setter,getter。同时对于java提供的类比如List,Map,因为类上并没有@ProtobufClass,无参构造 ,所以你需要有个包装类来对其进行包装,就像MVC中使用@RequestBody 需要有类来包装基本数据类型一样。ps(包装类中的List,Map的范型必须指定特定类型而不能使用Object或者不写并且要与实际的一致)

Jprotobuf 工具类测试使用

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

非常Amazing啊!对象成功输出了。

bash 复制代码
...
17:12:09.152 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - loading class done  'com.baidu.bjf.remoting.protobuf.Any$$JProtoBufClass'
HeartBeat(initiator=test, updateTime=1708679527671)

Process finished with exit code 0

你以为这就完了?众所周知,Protobuf的性能是非常强的,不做个简单测试怎么能行。

工具性能测试

ini 复制代码
 @Test
    public void testSerialization() throws IOException {
        HeartBeat heartBeat = new HeartBeat("test");
        ProtobufSerialization protobufSerialization = new ProtobufSerialization();
        HessianSerialization hessianSerialization = new HessianSerialization();

        Long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            byte[] bytes = protobufSerialization.serialize(heartBeat);
            HeartBeat deserialize = (HeartBeat) protobufSerialization.deserialize(bytes, Object.class);
        }
        log.info("protobuf序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            byte[] bytes = hessianSerialization.serialize(heartBeat);
            HeartBeat deserialize = (HeartBeat) hessianSerialization.deserialize(bytes, Object.class);
        }
        log.info("hessian序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);

    }

结果我麻了

ini 复制代码
17:25:50.135 [main] INFO Test1 - protobuf序列化和反序列化耗时:2313
17:25:50.298 [main] INFO Test1 - hessian序列化和反序列化耗时:163

是的,你没看错,hessian序列化整整比protobuf快了10倍多,说好的性能呢?我转念一想,会不会是反射导致的瓶颈。于是乎改了下代码

ini 复制代码
@Override
public <T> T deserialize(byte[] data, Class<T> clz) throws IOException {
    Codec<Any> anyCodec = ProtobufProxy.create(Any.class);
    anyCodec.decode(data);
    Any any = anyCodec.decode(data);
    String clazzName = any.getCodecClass().split("/")[1];
    Object res = any.unpack(HeartBeat.class);
    return (T) res;
}

结果测下来,好了一点,只比hessian序列化慢10倍了。

ini 复制代码
17:30:45.056 [main] INFO Test1 - protobuf序列化和反序列化耗时:1964
17:30:45.247 [main] INFO Test1 - hessian序列化和反序列化耗时:190

但是这还能用?我又想是不是Any的问题,于是乎又改了下测试代码,不再使用工具了,而是指定类型

java 复制代码
@Test
public void testSerialization() throws IOException {
    HeartBeat heartBeat = new HeartBeat("test");
    ProtobufSerialization protobufSerialization = new ProtobufSerialization();
    HessianSerialization hessianSerialization = new HessianSerialization();
    JsonSerialization jsonSerialization = new JsonSerialization();
     
    Codec<HeartBeat> codec = ProtobufProxy.create(HeartBeat.class);
    
    Long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        byte[] bytes = codec.encode(heartBeat);
        HeartBeat deserialize = codec.decode(bytes);
    }
    log.info("protobuf序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
    startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        byte[] bytes = hessianSerialization.serialize(heartBeat);
        HeartBeat deserialize = (HeartBeat) hessianSerialization.deserialize(bytes, HeartBeat.class);
    }
    log.info("hessian序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
}

结果

ini 复制代码
17:34:06.221 [main] INFO Test1 - protobuf序列化和反序列化耗时:57
17:34:06.341 [main] INFO Test1 - hessian序列化和反序列化耗时:118

破案了,就是Any导致的性能问题,所以想用Protobuf来序列化Object的想法还是洗洗睡吧.

相关推荐
Lenyiin12 分钟前
Linux 基础IO
java·linux·服务器
松☆29 分钟前
Dart 核心语法精讲:从空安全到流程控制(3)
android·java·开发语言
编码者卢布43 分钟前
【Azure Storage Account】Azure Table Storage 跨区批量迁移方案
后端·python·flask
编码者卢布1 小时前
【App Service】Java应用上传文件功能部署在App Service Windows上报错 413 Payload Too Large
java·开发语言·windows
q行1 小时前
Spring概述(含单例设计模式和工厂设计模式)
java·spring
好好研究2 小时前
SpringBoot扩展SpringMVC
java·spring boot·spring·servlet·filter·listener
毕设源码-郭学长2 小时前
【开题答辩全过程】以 高校项目团队管理网站为例,包含答辩的问题和答案
java
玄〤2 小时前
Java 大数据量输入输出优化方案详解:从 Scanner 到手写快读(含漫画解析)
java·开发语言·笔记·算法
tb_first3 小时前
SSM速通3
java·jvm·spring boot·mybatis