目录
- 🦁 什么是序列化和反序列化?
- 🦁 序列化和反序列化常见应用场景
- 🦁 序列化协议对应于 TCP/IP 4 层模型的哪一层?
- 🦁 常见序列化协议有哪些?
- 1. Java自带的序列化方式
- 2. Kryo
- 3.Hessian
🦁 什么是序列化和反序列化?
如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
简单来说:
- 序列化:将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
🦁 序列化和反序列化常见应用场景
- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
🦁 序列化协议对应于 TCP/IP 4 层模型的哪一层?
我们先来看各层模型的作用,如下:
OSI 七层协议模型中,表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话,就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么?
因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。
🦁 常见序列化协议有哪些?
1. Java自带的序列化方式
在Java中,要使用自带的序列号协议,只需要让需要进行序列化和反序列化的类实现 Serializable 接口即可。
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString
public class RpcRequest implements Serializable {
private static final long serialVersionUID = 1905122041950251207L;
private String requestId;
private String interfaceName;
private String methodName;
private Object[] parameters;
private Class<?>[] paramTypes;
private RpcMessageTypeEnum rpcMessageTypeEnum;
}
serialVersionUID 的作用:
- 序列化号 serialVersionUID 属于版本控制的作用。反序列化时,会检查 serialVersionUID 是否和当前类的 serialVersionUID 一致。如果 serialVersionUID 不一致则会抛出 InvalidClassException 异常。
- serialVersionUID只是用来被JVM识别,不会被序列号(被static修饰的变量不会被序列化)
如果不想序列号某些字段,只需要在字段前添加:transient 关键字
(只修饰变量)
其作用是阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
2. Kryo
Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积。使用如下:
@Slf4j
public class KryoSerializer implements Serializer {
/**
* Because Kryo is not thread safe. So, use ThreadLocal to store Kryo objects
*/
private final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.register(RpcResponse.class);
kryo.register(RpcRequest.class);
return kryo;
});
@Override
public byte[] serialize(Object obj) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Output output = new Output(byteArrayOutputStream)) {
Kryo kryo = kryoThreadLocal.get();
// Object->byte:将对象序列化为byte数组
kryo.writeObject(output, obj);
kryoThreadLocal.remove();
return output.toBytes();
} catch (Exception e) {
throw new SerializeException("Serialization failed");
}
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Input input = new Input(byteArrayInputStream)) {
Kryo kryo = kryoThreadLocal.get();
// byte->Object:从byte数组中反序列化出对象
Object o = kryo.readObject(input, clazz);
kryoThreadLocal.remove();
return clazz.cast(o);
} catch (Exception e) {
throw new SerializeException("Deserialization failed");
}
}
}
3.Hessian
Hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。Hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。
使用如下:
- 引入 Hessian2 相关依赖
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
- 创建 Hessian2 序列化/反序列化工具
public class Hessian2SerializationUtil {
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(outputStream);
output.writeObject(obj);
output.flush();
return outputStream.toByteArray();
}
public static <T> T deserialize(byte[] data, Class<T> clazz) throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
Hessian2Input input = new Hessian2Input(inputStream);
Object obj = input.readObject(clazz);
return clazz.cast(obj);
}
}
- 使用 Hessian2 序列化/反序列化工具进行数据的编解码
// 对象序列化成字节数组
MyObject obj = new MyObject();
byte[] data = Hessian2SerializationUtil.serialize(obj);
// 字节数组反序列化成对象
MyObject obj = Hessian2SerializationUtil.deserialize(data, MyObject.class);
需要注意的是,在使用 Hessian2 序列化/反序列化工具时,需要引入 Hessian2Output 和 Hessian2Input 两个类,并且序列化时需要将对象写入 ByteArrayOutputStream 中,反序列化时需要从 ByteArrayInputStream 中读取字节数组。另外,在反序列化时,需要指定对象的类型,Hessian2Input 在读取对象时,会根据这个类型信息进行对象的创建和类型检查。