keac's Bolg.

Java反序列化

字数统计: 1.2k阅读时长: 4 min
2022/05/25 Share

什么是反序列化和序列化

序列化合反序列化是什么呢,我们可以想象为比如我们从某宝买一张桌子或者柜子,这么大一个柜子肯定不会直接寄过来,那样太容易损坏而且也非常麻烦,一般来说的做法就是商家把桌子拆成一块块桌板寄过来,然后你自己拿到之后再进行拼接,那么商家在打包成木板的时候就是序列化的过程,反之你拆包装拼装桌子的时候就是反序列化的过程。

他的定义呢就是把一个Java类的一个实例序列化为数组(打包,方便传输),用于存储对象实例化信息:类成员变量合属性值。

因为Java序列化对象可以很方便的把对象转换成字节数组,又可以很方便的把字节数组反序列化成java对象,所以频繁的使用在Socket传输。在RMI和JMX服务中被强制性使用,在HTTP请求中也经常用到,如:直接接收序列化请求的后端服务、使用Base编码序列化字节字符串的方式传递等。

实现

在Java中实现对象反序列化非常简单,实现java.io.Serializable(内部序列化)java.io.Externalizable(外部序列化)接口即可被序列化,其中java.io.Externalizable接口只是实现了java.io.Serializable接口。

反序列化类对象时有如下限制:

  1. 被反序列化的类必须存在。
  2. serialVersionUID值必须一致。

除此之外,反序列化类对象是不会调用该类构造方法的,因为在反序列化创建类实例时使用了sun.reflect.ReflectionFactory.newConstructorForSerialization创建了一个反序列化专用的Constructor(反射构造方法对象),使用这个特殊的Constructor可以绕过构造方法创建类实例。

ObjectInputStream、ObjectOutputStream

java.io.ObjectOutputStream类最核心的方法是writeObject方法,即序列化类对象。

java.io.ObjectInputStream类最核心的功能是readObject方法,即反序列化类对象。

所以,只需借助ObjectInputStreamObjectOutputStream类我们就可以实现类的序列化和反序列化功能了。

java.io.Serializable

实现了Serializable的接口类,需要产生一个serialVersionUID常量,反序列化过程中如果双方的serialVersionUID 不一样会导致InvalidClassException异常,如果可序列化类没有显示声明,在运行的时候会基于类的各个方法自动计算生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package serializes;

import sun.rmi.server.DeserializationChecker;

import java.io.*;
import java.util.Arrays;

public class Test implements Serializable {
public String name;
public int age;
private String test;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getTest() {
return test;
}

public void setTest(String test) {
this.test = test;
}

public static void main(String[] args) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
//创建类并设置属性
Test test = new Test();
test.setName("test");
test.setAge(10);
test.setTest("test");
//创建Java序列化输出对象
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(test);
out.flush();
out.close();
//序列化的数组,可以存在文件里面或者进行Socket传输
System.out.println(Arrays.toString(baos.toByteArray()));
//利用对象生成的二进制数组创造输入,用于反序列化操作
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
//通过反序列化输入流,创建Java对象输入流
ObjectInputStream in = new ObjectInputStream(bais);
//反序列化
Test test1 = (Test) in.readObject();
System.out.println(test1.getName());
//关闭流
in.close();

} catch (Exception e) {
e.printStackTrace();
}
}
}

ObjectOutputStream序列化类对象的主要流程是首先判断序列化的类是否重写了writeObject方法,如果重写了就调用序列化对象自身的writeObject方法序列化,序列化时会先写入类名信息,其次是写入成员变量信息(通过反射获取所有不包含被transient修饰的变量和值)。

自定义反序列化

实现了java.io.Serializable接口的类,可以定义方法(魔术方法),会在反序列化或者序列化的时候调用

  1. private void writeObject(ObjectOutputStream oos),自定义序列化。
  2. private void readObject(ObjectInputStream ois),自定义反序列化。
  3. private void readObjectNoData()
  4. protected Object writeReplace(),写入时替换对象。
  5. protected Object readResolve()

具体的方法名定义在java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>),其中方法有详细的声明。

当我们对DeserializationTest类进行序列化操作时,会自动调用(反射调用)该类的writeObject(ObjectOutputStream oos)方法,对其进行反序列化操作时也会自动调用该类的readObject(ObjectInputStream)方法,也就是说我们可以通过在待序列化或反序列化的类中定义readObjectwriteObject方法,来实现自定义的序列化和反序列化操作,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是private

CATALOG
  1. 1. 什么是反序列化和序列化
  2. 2. 实现
    1. 2.1. ObjectInputStream、ObjectOutputStream
      1. 2.1.1. java.io.Serializable
    2. 2.2. 自定义反序列化