基础的反序列化一些总结(php和java)

什么是序列化和反序列化

序列化:将对象转换为数组或字符串等格式

反序列化:将数组或字符串等格式转换成对象

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)
相关推荐
冻柠檬飞冰走茶1 小时前
PTA基础编程题目集 7-8超速判断(C++语言实现)
开发语言·数据结构·c++·算法
机建狂魔2 小时前
Codex 接入第三方模型 API 实战:以 Mimo 为例
java·服务器·数据库·ai·ai编程·codex
山峰哥2 小时前
数据库工程与SQL调优:从慢查询到秒级响应的实战之路
java·开发语言·数据库·sql·深度优先·启发式算法
乐观的Terry2 小时前
11、发布系统-用户认证与权限体系
java·spring boot·spring·spring cloud·mybatis
前端开发张小七2 小时前
Java 学习笔记 · 第二课:面向对象核心(封装、继承、多态)及接口与异常
java·后端·程序员
wangjialelele2 小时前
Selenium4 + Java Web自动化测试入门指南:从环境搭建到常用操作详解
java·开发语言·前端·测试工具·自动化
Awna3 小时前
Golang 大小写可见性规范
开发语言·后端·golang
孙启超3 小时前
【AI应用开发】LangChain 中 Chain 和 Agent 核心区别?
java·人工智能·langchain·llm·rag·ai应用开发·agent loop
leoZ2313 小时前
实战复盘:用 Claude Code 从零搭一个 GitHub PR 统计工具
java·人工智能·python·深度学习·自然语言处理·github·llama