5.4实现一个基于 Kryo 的序列化器
上一节我们实现了一个通用的序列化框架,使得序列化方式具有了较高的扩展性,并且实现了一个基于 JSON 的序列化器。
但是,我们也提到过,这个基于 JSON 的序列化器有一个毛病,就是在某个类的属性反序列化时,如果属性声明为 Object 的,就会造成反序列化出错,通常会把 Object 属性直接反序列化成 String 类型,就需要其他参数辅助序列化。并且,JSON 序列化器是基于字符串(JSON 串)的,占用空间较大且速度较慢。
这一节我们就来实现一个基于 Kryo 的序列化器。那么,什么是 Kryo?
Kryo 是一个快速高效的 Java 对象序列化框架,主要特点是高性能、高效和易用。最重要的两个特点:
- 一是基于字节的序列化,对空间利用率较高,在网络传输时可以减小体积;
- 二是序列化时记录属性对象的类型信息,这样在反序列化时就不会出现之前的问题了。
5.4.1实现通用的序列化框架
添加依赖
<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; }
5.4.2替换序列化器测试
我们只需要把 NettyServer 和 NettyClient 责任链中的 CommonEncoder 传入的参数改成 KryoSerializer 即可使用 Kryo 序列化。
- pipeline.addLast(new CommonEncoder(new JsonSerializer())); + pipeline.addLast(new CommonEncoder(new KryoSerializer()));
最后运行之前的测试,测试结果与之前相同即没问题。