JavaBean到底什么时候需要序列化
目录:
- 什么是序列化
- 为什么要序列化
- 什么时候需要序列化
- 怎么序列化
- 什么时候需要实现Serializable
- ORM不需要实现Serializable
参考/来源:
什么是序列化
简单来说,我们把对象从内存中变成可存储或传输的过程称之为序列化
为什么要序列化
根本原因:需要将变量或对象从内存中取出来进行存储或传输
什么时候需要序列化
- 对象保存到文件或数据库
- 网络编程时对象跨平台跨语言传输,也即从windows上序列化的对象可到linux上反序列化,用c#序列化的对象可以被java反序列化。
- RPC远程接口调用
怎么序列化
JDK 原生序列化:实现Serializable
JDK类库中序列化和反序列化API
java.io.ObjectOutputStream:表示对象输出流。它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;
java.io.ObjectInputStream:表示对象输入流。它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;
JSON
XML
Hessian
Protobuf
序列化协议的选择指标为序列化性能、序列化后数据大小、协议本身兼容性(协议版本上下兼容性,跨语言兼容性)、安全性。
这几种协议对比如下:
- JSON、XML,可读性好,但是性能较差,序列化后占用空间大,序列化后数据无类型,需要反射才能获取对象类型;
- JDK通过InputStream、OutputSteeam来序列化、反序列化,性能也比较差;
- Hession、Protobuf性能都比较好,Hession对象兼容性更好,Protobuf更加高效。RPC框架一般选用这两种的比较多。
类属性修改对序列化的影响
系统的日常变更中,很可能会出现上游对RPC的请求参数或返回参数,增加一个字段或删除一个字段,甚至修改一个字段。需要考虑这些操作对线上服务的影响,是否影响序列化和反序列化。
serialVersionUID的作用
serialVersionUID属性是用来序列的标识符/反序列化的对象序列化类。
序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。
如果接收者为对象加载的类serialVersionUID与相应的发送者的类不同,则反序列化将导致 InvalidClassException。可序列化的类可以serialVersionUID通过声明一个serialVersionUID必须为static,final和type的字段来显式声明其自身long:private static final long serialVersionUID = 42L;
如果可序列化的类未显式声明一个 serialVersionUID,则序列化运行时将根据serialVersionUID该类的各个方面为该类计算默认值。基本是将类名,属性名,属性修饰符,继承的接口,属性类型,名称,方法,静态代码块等等都考虑进去了,然后再做hash运算
修改类的成员变量
序列化和反序列化的两端,在有serialVersionUID的前提下,对修改(新增,更新,删除)字段有兼容性,不影响序列化和反序列化的进行。
如果没有设置serialVersionUID,在修改字段后,会重新计算serialVersionUID,造成反序列化失败!
public class UserRequest implements Serializable {
private static final long serialVersionUID = -5567769020560997123L;
private String name;
private int age;
public UserRequest(String name) {
this.name = name;
}
}
public class SerializeTest {
/**
* 测试序列化
* @throws IOException
* @throws ClassNotFoundException
*/
@Test
public void test_serialize() throws IOException, ClassNotFoundException {
String filePath = "file.txt";
/**序列化*/
FileOutputStream fos = new FileOutputStream(filePath);
UserRequest userRequest = new UserRequest("qh");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(userRequest);
oos.flush();
oos.close();
/**反序列化*/
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
UserRequest readObject = (UserRequest) ois.readObject();
ois.close();
Assert.assertEquals("qh", readObject.getName());
}
/**
* 反序列化时增加字段
* 在UserRequest增加age字段
* @throws IOException
* @throws ClassNotFoundException
*/
@Test
public void test_update() throws IOException, ClassNotFoundException {
String filePath = "file.txt";
/**反序列化*/
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
UserRequest readObject = (UserRequest) ois.readObject();
ois.close();
Assert.assertEquals("qh", readObject.getName());
Assert.assertEquals(0, readObject.getAge());
}
/**
* 序列化时增加字段
* 在UserRequest删除name字段
* @throws IOException
* @throws ClassNotFoundException
*/
@Test
public void test_update2() throws IOException, ClassNotFoundException {
String filePath = "file.txt";
/**反序列化*/
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
UserRequest readObject = (UserRequest) ois.readObject();
ois.close();
Assert.assertEquals(0, readObject.getAge());
}
}
什么时候需要实现Serializable
转换成二进制字节流的形式
这种需要序列化的类必须实现Serializable。
常见的例子:把对象存储在Redis服务器中、RPC形式的远程方法调用(微服务使用Dubbo)转换成JSON字符串的形式
这种类就不需要实现Serializable了
常见的例子:后端暴露的接口返回的JSON格式对象、HTTP形式的远程方法调用(微服务使用的Feign)
ORM不需要实现Serializable
在普通的 Web 项目中,SpringMVC 负责将 POJO 放在响应体并返回给客户端,但我们没有让 DTO 实现Serializable
,这是因为它不需要在另一个 Java 应用中被重新创建。
没错,传输的是字节流,但并不是从一个对象序列化得到的,而是通过 Getter 方法拿出字段的值并转换成 JSON 格式的字符串,再对字符串进行序列化,最后传输。接收一个 POJO 类型的参数也是相同的道理。
同样地,Mybatis 也是通过 Getter/Setter
方法,拿出字段的值,或赋值给字段,必要情况下进行类型转换。在关系型数据库中,虽然一个对象对应着一条记录,但实际持久化的是对象的某些字段的值,而不是对象本身。(不过对于 NoSql 数据库,由于没有与 Java 基本类型对应的数据结构,就需要将对象进行序列化)