本文原载于我的博客,地址:https://blog.guoziyang.top/archives/65/,项目地址:https://github.com/CN-GuoZiyang/My-RPC-Framework
上一节我们实现了一个通用的序列化框架,使得序列化方式具有了较高的扩展性,并且实现了一个基于 JSON 的序列化器。
但是,我们也提到过,这个基于 JSON 的序列化器有一个毛病,就是在某个类的属性反序列化时,如果属性声明为 Object 的,就会造成反序列化出错,通常会把 Object 属性直接反序列化成 String 类型,就需要其他参数辅助序列化。并且,JSON 序列化器是基于字符串(JSON 串)的,占用空间较大且速度较慢。
这一节我们就来实现一个基于 Kryo 的序列化器。那么,什么是 Kryo?
Kryo 是一个快速高效的 Java 对象序列化框架,主要特点是高性能、高效和易用。最重要的两个特点,一是基于字节的序列化,对空间利用率较高,在网络传输时可以减小体积;二是序列化时记录属性对象的类型信息,这样在反序列化时就不会出现之前的问题了。
实现接口
首先添加 kryo 的依赖
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.2</version>
</dependency>
我们在上一节定义了一个通用的序列化接口:
public interface CommonSerializer {
byte[] serialize(Object obj);
Object deserialize(byte[] bytes, Class<?> clazz);
int getCode();
static CommonSerializer getByCode(int code) {
switch (code) {
case 1:
return new JsonSerializer();
default:
return null;
}
}
}
这里我们可以把 Kryo 的编号设为 0,后续会作为默认的序列化器,在静态方法的 switch 中加一个 case 即可。
根据接口,我们的主要任务就是实现其中的主要两个方法,serialize()
和 deserialize()
,如下:
public class KryoSerializer implements CommonSerializer {
private static final Logger logger = LoggerFactory.getLogger(KryoSerializer.class);
private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.register(RpcResponse.class);
kryo.register(RpcRequest.class);
kryo.setReferences(true);
kryo.setRegistrationRequired(false);
return kryo;
});
@Override
public byte[] serialize(Object obj) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Output output = new Output(byteArrayOutputStream)){
Kryo kryo = kryoThreadLocal.get();
kryo.writeObject(output, obj);
kryoThreadLocal.remove();
return output.toBytes();
} catch (Exception e) {
logger.error("序列化时有错误发生:", e);
throw new SerializeException("序列化时有错误发生");
}
}
@Override
public Object deserialize(byte[] bytes, Class<?> clazz) {
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Input input = new Input(byteArrayInputStream)) {
Kryo kryo = kryoThreadLocal.get();
Object o = kryo.readObject(input, clazz);
kryoThreadLocal.remove();
return o;
} catch (Exception e) {
logger.error("反序列化时有错误发生:", e);
throw new SerializeException("反序列化时有错误发生");
}
}
@Override
public int getCode() {
return SerializerCode.valueOf("KRYO").getCode();
}
}
这里 Kryo 可能存在线程安全问题,文档上是推荐放在 ThreadLocal 里,一个线程一个 Kryo。在序列化时,先创建一个 Output 对象(Kryo 框架的概念),接着使用 writeObject 方法将对象写入 Output 中,最后调用 Output 对象的 toByte() 方法即可获得对象的字节数组。反序列化则是从 Input 对象中直接 readObject,这里只需要传入对象的类型,而不需要具体传入每一个属性的类型信息。
最后 getCode 方法中事实上是把序列化的编号写在一个枚举类 SerializerCode
里了:
public enum SerializerCode {
KRYO(0),
JSON(1);
private final int code;
}
替换序列化器并测试
我们只需要把 NettyServer 和 NettyClient 责任链中的 CommonEncoder 传入的参数改成 KryoSerializer 即可使用 Kryo 序列化。
- pipeline.addLast(new CommonEncoder(new JsonSerializer()));
+ pipeline.addLast(new CommonEncoder(new KryoSerializer()));
最后运行之前的测试,测试结果与之前相同即没问题。
欢迎关注我的微信公众号:楚狂声哥,更新各种有深度的八股文!