jackson反序列化多态对象死循环本质是类型解析失控引发对象图无限递归构建与堆内存持续增长;常见于@jsontypeinfo(id.class)无白名单、泛型推导歧义、@jsonidentityinfo缺失引用锚点等场景。

Jackson反序列化多态对象时出现死循环,本质是类型解析逻辑失控导致对象图无限递归构建,最终引发堆内存持续增长甚至OOM。这不是简单的栈溢出,而是反序列化器在重建对象过程中反复创建中间代理、嵌套容器或未终止的引用链,使GC无法回收。
多态配置不当触发无限递归
常见于以下场景:
-
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 直接暴露全类名,且未限制白名单——攻击者构造含自引用的恶意 JSON(如
{"@class":"java.util.HashMap","@class":"java.util.HashMap"}),Jackson 会尝试反复加载并实例化同一类,形成隐式循环 - 基类字段声明为
Object或泛型通配符(List>、Map<string></string>),又开启enableDefaultTyping()—— Jackson 在推断子类型时陷入类型歧义,不断尝试不同解析路径,生成大量临时 TypeReference 和 TypeDeserializer 实例 - 使用
@JsonIdentityInfo但未配合@JsonBackReference/@JsonManagedReference处理双向关联,在多态结构中丢失引用锚点,导致反序列化器误判为新对象而重复展开
嵌套过深 + 无约束的泛型推导
当 JSON 中存在深度嵌套的多态结构(如 {"type":"node","children":[{"type":"node","children":[...]}]}),且 Jackson 配置未设限时:
- 每层嵌套都触发一次
TypeDeserializer.deserializeTypedFromObject()调用,伴随反射扫描、类加载、缓存查找 - 泛型擦除后,Jackson 需动态构造
CollectionType或MapType,若嵌套层级 >8 层,JavaType对象本身就会占用 MB 级内存 - 未设置
DeserializationFeature.FAIL_ON_INVALID_SUBTYPE,错误子类型不报错而是静默 fallback 到Object,进一步加剧类型推导负担
如何切断循环链并控制膨胀
关键不是“避免多态”,而是让多态行为可预测、有边界:
- 禁用不安全的默认多态:
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); objectMapper.disable(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY); - 显式限定多态范围:只对必要接口/抽象类启用
@JsonTypeInfo,且必须搭配@JsonSubTypes列出全部合法子类,禁止运行时动态发现 - 设置硬性递归保护:
objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true); objectMapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, true); - 对高风险字段(如
data: Object)改用自定义反序列化器,内部做类型预检和深度计数,超 5 层直接抛异常
替代方案:用结构化代替泛型多态
真正需要灵活类型的场景,应放弃 Object 或宽泛泛型,转为明确协议:
- 前端传
{"type":"user","payload":{...}},后端用Map<string object></string>解析外层,再根据type字段分发到具体 DTO(如UserDTO.class)二次解析 - 用
@JsonUnwrapped+ 枚举路由替代复杂继承树,减少 Jackson 运行时类型分析压力 - 批量数据统一走
JsonNode解析,业务层按需转换,避开反序列化器的类型推导环节
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











