java反序列化循环引用对象图的关键是复用已创建对象实例,原生objectinputstream支持该机制,但需序列化端启用引用共享、类结构稳定且无恶意类型;替代方案推荐gson+graphadapterbuilder处理json等跨语言场景。

Java 反序列化复杂循环引用对象图,关键不是“绕过”循环,而是让反序列化器能识别并复用已创建的对象实例——原生 ObjectInputStream 本身就支持这点,但前提是序列化端也用了兼容机制,且类结构稳定、无恶意类型。
确保序列化阶段就启用引用共享
如果序列化时没保留对象引用关系,反序列化时必然重建多个副本或抛异常。Java 原生序列化默认开启引用共享(reference sharing),但需满足:
- 整个对象图必须由同一个
ObjectOutputStream实例写出(不能分多次、跨流写) - 所有参与类都实现
Serializable,且未定义writeObject/readObject破坏默认逻辑 - 避免在自定义
writeObject中手动调用defaultWriteObject()后又重复写字段,导致引用丢失
反序列化时信任类路径与版本一致性
ObjectInputStream 在还原对象图时,靠 serialVersionUID 校验版本,并通过内部句柄表(handle table)管理已读对象。若失败,常见原因有:
- 类被修改但未更新
serialVersionUID,导致反序列化拒绝加载 - 反序列化环境缺少某个被引用的类(如第三方库版本不匹配),抛
ClassNotFoundException - 使用了
readUnshared()—— 它禁用引用共享,每次强制新建对象,会破坏循环结构
建议显式设置 serialVersionUID,并避免在生产中动态生成;对不可信来源的数据,绝不直接用 ObjectInputStream。
替代方案:用 Gson + GraphAdapterBuilder 处理非原生场景
当数据来自 JSON(如 HTTP API)、或需跨语言互通时,Java 原生序列化不适用。此时推荐 Gson 的 GraphAdapterBuilder:
- 它把对象图转为“ID-对象”映射 + ID 引用,天然支持任意深度循环
- 无需修改业务类,不依赖
Serializable,也不触发构造器 - 使用时注册一次即可:
new GsonBuilder().registerTypeAdapterFactory(new GraphAdapterBuilder().create()).create()
注意:它只解决序列化/反序列化环节的栈溢出,不提供安全沙箱——若 JSON 含恶意类型(如 java.lang.Class),仍需配合 TypeAdapterFactory 白名单过滤。
警惕反序列化漏洞,尤其在开放接口中
循环引用本身不危险,但它是反序列化攻击的“放大器”。攻击者可构造深层嵌套+循环引用的恶意 payload,触发 readObject 中的 gadget 链。防护要点:
- 永远不在反序列化不可信输入(如用户上传、网络请求体)时用
ObjectInputStream - 改用 JSON/XML 等文本格式,并配置解析器禁用动态类型(如 Jackson 的
enableDefaultTyping必须关闭) - 必要时使用
ValidatingObjectInputStream或自定义resolveClass,只允许白名单内的类
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











