java反序列化小记
🧾 整体目标
将一个
Person对象转换为字节流(序列化),再从字节流还原成对象(反序列化),验证 Java 序列化机制的行为。
🔹 第一部分:序列化(把对象变成字节)
// 1. 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
Person p1 = new Person();
p1.name = "Alice";
oos.writeObject(p1);
逐行解释:
| 代码 | 作用 |
|---|---|
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | 创建一个内存中的字节输出流,用于临时存储序列化后的数据(不写入文件或网络) |
ObjectOutputStream oos = new ObjectOutputStream(baos); | 包装 baos,创建一个对象输出流,专门用于写入可序列化的对象 |
Person p1 = new Person(); | 创建一个 Person 实例(会调用构造函数) |
p1.name = "Alice"; | 给字段赋值 |
oos.writeObject(p1); | 将 p1 序列化为字节,并写入 baos |
✅ 此时,baos 中保存了 p1 的完整状态(字段名 + 字段值 + 类信息 + serialVersionUID 等)
💡 注意:如果
Person没有实现Serializable接口,这一步会抛出NotSerializableException。
🔹 第二部分:反序列化(把字节变回对象)
// 2. 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);System.out.println("开始反序列化...");
Person p2 = (Person) ois.readObject(); // ← 关键行
System.out.println("反序列化完成");
逐行解释:
| 代码 | 作用 |
|---|---|
baos.toByteArray() | 获取序列化后的原始字节数组 |
ByteArrayInputStream bais = new ByteArrayInputStream(...); | 创建一个内存中的字节输入流,用于读取这些字节 |
ObjectInputStream ois = new ObjectInputStream(bais); | 包装 bais,创建对象输入流,用于读取对象 |
ois.readObject() | 从流中读取字节,并重建对象 → 这是反序列化的入口 |
🔥 关键:JVM 在 ois.readObject() 中自动调用了哪些方法?
当 ois.readObject() 执行时,JVM 会按顺序检查并自动调用以下特殊方法(如果存在):
✅ 1. readObject(ObjectInputStream in)
- 签名必须严格匹配:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException - 作用:自定义反序列化逻辑(如校验、解密、初始化 transient 字段)
- 调用时机:在恢复字段值之后(如果你调用了
in.defaultReadObject())或之前 - 权限:即使
private,JVM 也会通过反射调用
✅ 2. readResolve()
- 签名
private Object readResolve() throws ObjectStreamException - 作用:替换反序列化出来的对象(常用于单例模式保护、代理对象返回等)
- 调用时机:在
readObject完成后、返回对象前 - 示例:
private Object readResolve() {return MySingleton.getInstance(); // 确保反序列化不会破坏单例 }
❌ 不会调用的方法:
- 构造函数(Constructor):反序列化不调用任何构造函数(包括无参构造)
- 普通方法:除非你在
readObject中显式调用
📌 补充:序列化时也会自动调用的方法
对应地,在 oos.writeObject(p1) 时,JVM 也会检查:
✅ writeObject(ObjectOutputStream out)
- 自定义序列化逻辑(如加密字段、跳过某些字段)
- 签名:
private void writeObject(ObjectOutputStream out) throws IOException
✅ writeReplace()
- 替换要被序列化的对象(类似
readResolve的反向)
🧪 完整流程图(反序列化阶段)
调用 ois.readObject()↓
JVM 检查 Person 类是否有 readObject()↓
是 → 通过反射调用 Person.readObject(in)↓
在 readObject 中通常调用 in.defaultReadObject() → 恢复字段值↓
JVM 检查是否有 readResolve()↓
是 → 调用 readResolve(),用其返回值作为最终对象↓
返回对象给 p2
✅ 总结:你这段代码的作用
| 步骤 | 作用 |
|---|---|
| 序列化 | 将 Person("Alice") 转为字节数组,存入内存流 |
| 反序列化 | 从字节数组重建 Person 对象,不调用构造函数,但会自动回调 readObject()(如果存在) |
| 安全意义 | 如果 readObject() 中包含危险操作(如执行命令、触发反射链),攻击者可通过伪造字节流实现 RCE |
💡 这就是为什么 “不要反序列化不可信数据” 是 Java 安全的黄金法则!
