如何使用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的想法还是洗洗睡吧.