java反序列化浅析

和PHP的序列化一样,都是为了想把对象转成一个字节序列,这样方便持久化存储,必免程序运行结束后对象就从内存里消息,并且将对象序列化后也便于网络传输。

既然是java反序列化,那么我们就要了解在java中是怎么实现序列化的

序列化

java.io.ObjectOutputStream

java.io.ObjectOutputStream 类 写对象 writeObject()

使用该方法对参数指定的obj对象进行序列化,把字节序列写到一个目标输出流中,按照Java的标准约定是给文件一个.ser扩展名

反序列化

java.io.ObjectInputStream

java.io.ObjectInputStream 类 读对象 readObject()

该方法从一个源输入流中读取字节序列,再把他们反序列化为一个对象,并将其返回

特征参考

一段数据以rO0AB开头,你基本可以确定这串就是Java序列化base64加密的数据

或者如果以aced开头,那么它就是这一段java序列化的16进制数据

那我们使用java代码实习一下序列化和反序列化

Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package SerialTest;


import java.io.Serializable;

class Person implements Serializable {
private String name;
private int age;
private String sex;
private int stuld;
//private transient stuld; 假如学号属性加上了 transient关键字,则该属性不会被序列化和反序列化
//private static stuld; 假如学号属性加上了 static关键字,则该属性也不会被序列化和反序列化

public Person(String name, int age, String sex, int stuld) {
this.name = name;
this.age = age;
this.sex = sex;
this.stuld = stuld;
}

}

Serializable接口

可以发现在这我使用了Serializable接口,这个接口跟进一下,发现里面其实是空的,没有任何方法,这是因为Serializable接口仅仅只是做了一个标记,真正的序列化动作不是靠它完成的。

image-20220502134200597

Java原生实现了一套序列化的机制,它让我们不需要额外编写代码,只需要实现java.io.Serializable接口,并调用ObjectOutputStream类的writeObject方法即可

1
2
3
4
5
6
public static void main(String[ ] args) throws IOException(
Person p = new Person();
0bjectOutputStream ObjectOutputStream = new ObjectOutputStream(
new FileOutputStream( name:"ser.ser"));
ObjectOutputStream.writeObject(p);
ObjectOutputStream.close();

跟进writeObject函数

image-20220428171050780

我们通过阅读它的注释可以得知,在序列化的过程当中,是针对对象本身,而非针对类的,因此静态属性是不参与序列化和反序列化的过程的。另外,如果属性本身声明了transient关键字,也会被忽略。但是如果某对象继承了A类,那么A类当中对象的对象属性也是会被序列化和反序列化的(前提是A类也实现了java.io.Serializable接口)

对Person对象序列化和反序列化

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
package SerialTest;

import java.io.*;

public class SerializableTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
serialPerson();
//反序列化
Person person = deseialPerson();
System.out.println(person);

}

// Person对象序列化
private static void serialPerson() throws IOException {
Person person = new Person("xicc",100,"男",1001);

ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(new File("d:/oos.txt"))
);
oos.writeObject(person);
System.out.println("person对象序列化成功!");
oos.close();
}

// Person对象反序列化
private static Person deseialPerson() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(new File("d:/oos.txt"))
);
Person person = (Person) ois.readObject();
System.out.println("person对象反序列化成功!");
return person;
}
}



序列化结果为:

 sr SerialTest.Person捠??漷 I ageI stuldL namet Ljava/lang/String;L sexq ~ xp d 閠 xicct 鐢?

这样简单的序列化和反序列化很容易实现,但是假如我在序列化之后,再去Person对象里新增一个字段,然后再反序列化,看看是啥效果

image-20220502134919990

可以发现这里发生了异常,显示serialVersionUID不一样

image-20220502135022941

serialVersionUID是什么?

serialVersionUID可以把它想象成一个UID号码,这个号码是对象序列化前后的唯一标识符,如果没有显示为它定义,那么编译器就会自动为它声明一个。