java序列化遇循环引用直接导致stackoverflowerror;常用解法包括注解跳过(@jsonignore)、id映射(graphadapterbuilder)、启用循环检测(setcycledetectionenabled)、重构tostring及实体设计、全局自定义序列化器。

Java 序列化时遇到循环引用,最直接的后果就是 StackOverflowError——不是异常,无法 catch,JVM 会直接崩溃。核心问题在于:默认的 JSON 库(如 Jackson、Gson、FastJSON)和 toString() 都是递归遍历对象图,一旦 A→B→A 这类闭环存在,调用链就无限展开。
用注解跳过循环字段(最常用)
这是后端开发中最轻量、最可控的方式,适用于 JPA/Hibernate 实体等双向关联场景:
- Jackson:在可能引发循环的字段上加
@JsonIgnore,或在 getter 方法上标注 - FastJSON:使用
@JSONField(serialize = false)或@JSONField(deserialize = false)控制单向序列化 - 如果要用双向关联但又不想丢数据,可配合
@JsonManagedReference/@JsonBackReference(Jackson),前者正向序列化,后者只反序列化不输出
启用库内置的循环检测机制
部分主流库提供开箱即用的保护能力,无需改代码结构:
- Gson:引入
GraphAdapterBuilder,它把对象转成 ID 映射表,引用用 ID 表示,彻底切断递归链 - Apache Commons Lang:用
ReflectionToStringBuilder并设置setCycleDetectionEnabled(true),适合调试时安全打印 - Jackson:配置
ObjectMapper的SerializationFeature.FAIL_ON_EMPTY_BEANS虽不防循环,但配合SimpleModule自定义序列化器可实现 ID 替换逻辑
重构 toString 和实体设计(治本)
很多栈溢出其实源于开发阶段没意识到 toString 也参与了序列化流程(比如日志打印 + 接口返回同时触发):
- Lombok 的
@ToString默认不递归,但若用了of = {...}显式包含关联对象,就得手动排除,例如@ToString(exclude = "parent") - JPA 实体中,
@OneToMany(mappedBy = "...")侧建议加@ToString.Exclude或直接不重写 toString - 更进一步:把关联关系抽为 ID 字段(如
departmentId)而非对象引用,既避免循环,又降低耦合
统一 JSON 序列化入口做兜底
在 Spring Boot 等框架中,可通过全局配置拦截所有响应,避免每个 DTO 都重复处理:
- Jackson:注册
SimpleModule,添加自定义序列化器,对已见过的对象返回"[CircularRef]"占位符 - 用
ThreadLocal<set>></set>记录当前线程中已遍历的对象实例,每次序列化前检查是否已出现过 - 生产环境建议开启
spring.jackson.serialization.write_dates_as_timestamps=false等基础防护,再叠加循环控制
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











