什么是序列化和反序列化
序列化:将对象转换为数组或字符串等格式
反序列化:将数组或字符串等格式转换成对象
php序列化和反序列化
PHP中对应的函数:
serialize() //将对象转换成一个字符串
unserialize() //将字符串还原成一个对象
序列化输出格式

魔术方法
要想弄懂反序列化怎么进行的就要先了解魔术方法
下边是我看B站小迪视频总结的一些php魔术方法
_construct和_destruct

输出结果

_sleep
用到serialize时会自动调用

_wakeup
调用unserialize时会使用(用于反序列化恢复对象之前)

_INVOKE 将对象当做函数执行时执行

结果

_toString 对象类中存在该方法,该对象类被当做字符串处理时会触发

echo只能输出的只用字符串
_CALL 若有一个方法存在则直接调用,该方法不存在则调用_call函数

结果

_get 读取对象的属性时若存在则返回属性值,不存在则调用_get函数


_set 设置一个对象的属性时若存在则返回属性值,不存在则调用_get函数



_isset 检测对象的某个函数是否存在时执行此函数。对不可访问的属性调用时用_isset()或empty()时,会被调用


_unset 在不可访问的属性上使用unset()是触发,摧毁对象的某个属性时执行
更inset差不多

各个魔术方法调用条件
反序列化的常见起点
| 方法名 | 调用条件 |
|---|---|
__wakeup |
一定会调用 |
__destruct |
一定会调用 |
__toString |
当一个对象被反序列化后又被当做字符串使用 |
反序列化常见跳板
| 方法名 | 调用条件 |
|---|---|
__toString |
当一个对象被当做字符串使用 |
__get |
读取不可访问或不存在的属性时被调用 |
__set |
当给不可访问或不存在的属性赋值时被调用 |
__isset |
对不可访问或不存在的属性调用isset()或empty()时被调用 |
反序列化常见终点
| 方法名 | 调用条件 |
|---|---|
__call |
调用不可访问或不存在的方法时被调用 |
call_user_func |
一般php代码执行都会选择这里 |
call_user_func_array |
一般php代码执行都会选择这里 |
PHP属性类型
public(公共的):在本类内部、外部类、子类都可以访问
protected(受保护的):只有本类或子类或父类中可以访问
private(私有的):只有本类内部可以使用
序列化数据显示:
public 属性序列化的时候格式是正常成员名
private 属性序列化的时候格式是 %00类名%00成员名
protected 属性序列化的时候格式是 %00*%00成员名
(%00 表示 ASCII 码为 0 的空字符(NUL),在序列化字符串中不可见,但在长度计算中占一个字节。
例如,类名 User 中的私有属性 $name,序列化后显示为 %00User%00name;保护属性 $age 显示为 %00*%00age。)
例子
php
class Test {
public $publicProp = "nihao";
public $protectedProp = "woshi";
public $privateProp = "wgego";
}
$test = new Test();
echo serialize($test);
输出结果:
php
O:4:"Test":3{s:10:"publicProp";s:5:"nihao";s:14:"protectedProp";s:5:"woshi";s:11:"privateProp";s:5:"wgego";}
php
class Test {
public $publicProp = "nihao";
protected $protectedProp = "woshi";
private $privateProp = "wgego";
}
$test = new Test();
echo serialize($test);
结果
php
O:4:"Test":3{s:10:"publicProp";s:5:"nihao";s:13:"*protectedProp";s:5:"woshi";s:19:"TestprivateProp";s:5:"wgego";}
Java序列化
序列化:将对象转换成字节流
java
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file"));
oos.writeObject(obj);
反序列化:将字节流还原成对象
java
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file"));
Object obj = ois.readObject();
Java序列化输出格式
Java序列化输出的是二进制字节流(不可读),与PHP的文本字符串完全不同。
关键标识 :序列化后的二进制文件头部固定为 AC ED 00 05(十六进制)
java
import java.io.*;
class Test implements Serializable {
public String publicProp = "nihao";
protected String protectedProp = "woshi";
private String privateProp = "wgego";
}
public class Main {
public static void main(String[] args) throws Exception {
Test test = new Test();
// 序列化到文件
FileOutputStream fos = new FileOutputStream("test.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(test);
oos.close();
// 读取字节流(十六进制查看)
FileInputStream fis = new FileInputStream("test.ser");
byte[] bytes = fis.readAllBytes();
for (byte b : bytes) {
System.out.printf("%02X ", b);
}
// 输出开头:AC ED 00 05 ...
}
}
结果
AC ED 00 05 73 72 00 04 54 65 73 74 14 3A 4B 5C 6D 7E 8F 90 02 00 03 4C 00 0B 70 72 69 76 61 74 65 50 72 6F 70 74 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 3B 4C 00 0C 70 72 6F 74 65 63 74 65 64 50 72 6F 70 71 00 7E 00 01 4C 00 0A 70 75 62 6C 69 63 50 72 6F 70 71 00 7E 00 01 78 70 74 00 05 77 67 65 67 6F 74 00 05 77 6F 73 68 69 74 00 05 6E 69 68 61 6F
Java序列化关键概念
| 概念 | 说明 |
|---|---|
| 接口要求 | 类必须实现 java.io.Serializable 接口 |
serialVersionUID |
版本控制标识,反序列化时用于验证类版本一致性 |
transient |
修饰的属性不会被序列化(类似PHP中的某些属性可被忽略) |
writeObject() / readObject() |
自定义序列化/反序列化过程 |
Java中的"魔术方法"
Java没有PHP那种动态的魔术方法,但有等效的机制:
| PHP | Java等效 |
|---|---|
__construct |
构造函数(public Test()) |
__destruct |
finalize() 方法(不推荐使用) |
__wakeup |
readObject() 方法(反序列化时自动调用) |
__sleep |
writeObject() 方法(序列化时自动调用) |
ObjectInputStream.readObject() ------ 原生反序列化入口
java
import java.io.*;
class User implements Serializable {
private static final long serialVersionUID = 1L;
public String name = "admin";
private String password = "123456";
@Override
public String toString() {
return "User{name='" + name + "', password='" + password + "'}";
}
}
public class TestSerializable {
public static void main(String[] args) throws Exception {
User user = new User();
// ===== 序列化 =====
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(user);
oos.close();
byte[] data = baos.toByteArray();
System.out.println("序列化后的二进制数据(十六进制前32字节):");
for (int i = 0; i < 32 && i < data.length; i++) {
System.out.printf("%02X ", data[i]);
}
System.out.println("...");
// ===== 反序列化 =====
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais);
User restored = (User) ois.readObject();
ois.close();
System.out.println("反序列化后的对象: " + restored);
}
}
结果
序列化后的二进制数据(十六进制前32字节):
AC ED 00 05 73 72 00 04 55 73 65 72 4C B3 8A 7E 9F 1A 2F 4D 8E 02 00 02 4C 00 04 6E 61 6D 65 74 ...
反序列化后的对象: User{name='admin', password='123456'}
java.io.Serializable ------ 序列化门票
任何类想要被 ObjectInputStream.readObject() 反序列化,必须实现 Serializable 接口:
java
import java.io.Serializable;
// 门票:实现Serializable接口
class User implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
private String password;
}
XMLDecoder ------ XML格式反序列化入口
JDK自带的XML反序列化工具,通过XML描述对象结构:
java
import java.beans.XMLDecoder;
import java.io.FileInputStream;
// XML格式的反序列化
XMLDecoder decoder = new XMLDecoder(new FileInputStream("user.xml"));
User user = (User) decoder.readObject(); // 危险入口
decoder.close();
对应的XML格式示例:
XML
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0" class="java.beans.XMLDecoder">
<object class="User">
<void property="name">
<string>admin</string>
</void>
</object>
</java>
SnakeYaml ------ YAML格式反序列化入口
第三方库,用于解析YAML格式并反序列化成Java对象:
XML
// 需要导入:org.yaml:snakeyaml
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
// YAML格式的反序列化
Yaml yaml = new Yaml(new Constructor(User.class));
User user = yaml.load("name: admin\npassword: 123456");
XML
!!User
name: admin
password: 123456
Java反序列化示例(含readObject)
java
import java.io.*;
class User implements Serializable {
private static final long serialVersionUID = 1L;
public String username;
protected String email;
private String password;
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
// 类似PHP的__wakeup:反序列化时自动调用
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 默认反序列化
System.out.println("[!] readObject被调用,类似PHP的__wakeup");
// 可在此添加额外逻辑
}
// 类似PHP的__sleep:序列化时自动调用
private void writeObject(ObjectOutputStream oos)
throws IOException {
System.out.println("[!] writeObject被调用,类似PHP的__sleep");
oos.defaultWriteObject(); // 默认序列化
}
@Override
public String toString() {
return "User{username='" + username + "', email='" + email + "'}";
}
}
public class SerializeDemo {
public static void main(String[] args) throws Exception {
User user = new User("admin", "admin@test.com", "123456");
// ===== 序列化 =====
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(user); // 触发writeObject
oos.close();
byte[] serializedData = baos.toByteArray();
System.out.println("序列化后的字节长度: " + serializedData.length);
// ===== 反序列化 =====
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
ObjectInputStream ois = new ObjectInputStream(bais);
User restoredUser = (User) ois.readObject(); // 触发readObject
ois.close();
System.out.println("反序列化后的对象: " + restoredUser);
}
}
结果

java属性
| 属性类型 | 序列化格式 |
|---|---|
public |
正常序列化,可作为字段名直接引用 |
protected |
正常序列化,保存时包含访问标识 |
private |
正常序列化,但通过反射获取 |
transient |
不序列化(类似PHP中未定义) |
Java反序列化常见"起点/跳板/终点"
| 阶段 | Java对应机制 | 触发条件 |
|---|---|---|
| 起点 | readObject() |
反序列化时一定会调用 |
| 起点 | readResolve() |
反序列化完成后,替换返回对象时调用 |
| 跳板 | toString() |
对象被当作字符串时自动调用 |
| 跳板 | 反射(getField/setField) |
读取/设置属性时触发 |
| 终点(危险) | InvocationHandler / Proxy |
动态代理,可执行任意代码 |
| 终点(危险) | TemplatesImpl / Transformer |
常见利用链(如CommonsCollections) |