resolveClass 是反序列化中类名到类对象映射的关键控制点,通过重写该方法可实现包名迁移、版本兼容或安全拦截;它在反序列化开始前执行,决定使用哪个 Class 加载对象,不同于后续的 resolveObject 实例替换机制。

ObjectInputStream 在反序列化时,并不会直接按字节流里记录的类名去加载类,而是通过 resolveClass 方法这一“解析钩子”来决定最终使用哪个 Class 对象。这个方法就是整个反序列化过程中**类名到类对象映射的关键控制点**,也是实现类名动态替换(比如兼容旧包名、桥接不同版本、甚至安全拦截)的核心机制。
resolveClass 是反序列化的第一道类加载关卡
当 ObjectInputStream 读取到一个类描述符(ObjectStreamClass)后,它会调用 resolveClass(ObjectStreamClass desc),把类名、签名等元信息传入。默认实现是调用当前 ObjectInputStream 实例的 class loader 的 loadClass(String name)。但你可以重写它——只要继承 ObjectInputStream 并覆盖该方法,就能在类真正加载前介入。
- 传入的
desc.getName()是序列化时写入的原始类全限定名(例如"com.oldpackage.User") - 返回值必须是非 null 的 Class 对象,否则抛出 ClassNotFoundException
- 返回的 Class 必须与 desc 描述的序列化版本(serialVersionUID、字段结构)兼容,否则后续反序列化会失败
用 resolveClass 实现包名迁移或别名映射
常见场景是服务升级后把类从 com.oldpackage.User 搬到了 com.newpackage.User,但老数据仍需读取。这时可在 resolveClass 中做字符串映射:
- 检查
desc.getName().equals("com.oldpackage.User") - 手动尝试加载
"com.newpackage.User"(注意用 same class loader) - 成功则返回新类;失败则 fallback 到 super.resolveClass(desc) 继续走默认逻辑
这种映射不改变字段布局,只换“马甲”,前提是两个类的 serialVersionUID 相同、字段定义一致(包括类型、顺序、可访问性)。
resolveClass 不等于 resolveObject,二者职责不同
有人混淆 resolveClass 和 resolveObject:
- resolveClass 控制「哪个 Class 被用来构造实例」,发生在反序列化**开始前**,影响整个对象图的类型解析;
- resolveObject 控制「反序列化出来的对象实例是否被替换成另一个对象」,发生在对象字段填充**完成后**,属于实例级替换(如单例代理、脱敏包装)。
两者可以共存:先用 resolveClass 指定用新类加载,再用 resolveObject 把实例转成不可变封装。
安全风险与注意事项
重写 resolveClass 时需警惕:
- 不要无条件信任 desc.getName(),攻击者可能伪造恶意类名(如 "javax.swing.JFrame")触发危险类加载;建议白名单校验或拒绝非预期包名;
- 若返回的 Class 与 desc 的 serialVersionUID 不匹配,readObject 会抛 InvalidClassException;
- 自定义 resolveClass 不影响 ObjectInputStream 内部对基础类型(String、Enum、数组等)的处理逻辑;
- 使用线程上下文类加载器(Thread.currentThread().getContextClassLoader())需谨慎,ObjectInputStream 默认用的是创建时绑定的 loader。


















