DEBUG.WIKI
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
雨云;给项目一台云服务器
晚安云;香港 CN2 ¥15/月

Jackson 3 复用 builder 后新增模块不生效

Jackson 3.0.3 的 builder 会缓存首次 build 状态,随后添加模块可能没有应用。3.0.4 已让模块修改失效缓存;旧环境可分别创建 builder,并完整保留每份 mapper 所需配置。

使用 Jackson 3.0.3 的 JsonMapper builder 生成第一份 mapper 后,再给同一个 builder 添加 serializer 或 deserializer 模块,第二次 build 得到的 mapper 可能仍只有第一次的模块行为。添加调用没有抛错,真正序列化时却继续使用旧配置,容易误判成模块根本没有注册成功。

这里要区分两种情况:模块在首次 build 之前就没有生效,和首次 build 正常、后续改变模块才失效。后者需要检查 builder 内部保存的状态。正式 3.0.3 与 3.0.4 之间有针对这条路径的修正;不能把其他版本的任意模块问题都套进来。

模块列表变了,缓存的构建状态没失效

Jackson 3 将模块保存在 builder 中,构建 mapper 时才应用。saveStateApplyModules() 会保存 _savedState;已经有保存状态时,不再走相同的模块应用分支。

在 3.0.3 中,添加模块或移除全部模块没有同步清掉这份已保存状态,因此后续 build 可以忽略两次构建之间的模块变化。3.0.4 在 addModule、removeAllModules 路径中清除 _savedState,使下一次构建重新处理配置。

模块是否进入 mapper 与某一条 JSON 的具体输出不是同一个检查。还要确认模块本来就覆盖了目标类型、没有被后面的模块覆盖,并且实际调用的确实是第二份 mapper。

旧环境先分别创建 builder

暂时不能更新依赖时,可以让不同配置各自从新 builder 构造,不复用已经 build 过的可变对象。下面是配置方法中的 Java 片段;baseModule、extraModule 代表项目已有的真实模块,不是要额外创建一套空模块:

import tools.jackson.databind.json.JsonMapper;

JsonMapper baseMapper = JsonMapper.builder()
    .addModule(baseModule)
    .build();

JsonMapper extendedMapper = JsonMapper.builder()
    .addModule(baseModule)
    .addModule(extraModule)
    .build();

两份 builder 还应分别带上需要的命名策略、特性、mix-in 和其他配置。只重建模块列表,却漏掉原来的时间格式或可见性设置,可能让别的字段发生变化。配置较多时,可由项目已有的构建函数统一应用公共设置,但每次返回新的 builder。

这里的扩展 mapper 明确需要两个模块。addModule、addModules 是增加模块,不是“以这一批替换全部旧模块”的通用 setter;相同 registration ID 的模块也有替换规则。不能因为调用发生在第二次 build 前,就认为先前模块会自动消失。需要不同集合时,最直观的是在新 builder 上列明完整集合和顺序。

更新后仍要检查实际序列化入口

3.0.4 是已经包含状态失效修正的正式版本。维护升级应按项目的 Jackson 版本线和兼容要求进行,不必为了这个旧回归把已使用的较新兼容版本降回 3.0。保留依赖清单和公共配置;Jackson 2 的即时注册方式也不能直接用来推断 Jackson 3 的 builder 行为。

若通过 Spring Boot 输出 JSON,还要确认 HTTP 转换器选择的 mapper。默认 JsonMapper 与自定义 Bean 的选择可能导致另一类“直接调用有效、HTTP 无效”;更换 builder 不会自动改变框架选用的对象。

验收应让同一组有代表性的对象分别经过基础 mapper 和扩展 mapper,检查自定义类型的读写行为、模块顺序,以及基础 mapper 没有被后续构建意外改变。只打印模块名称,不足以证明 serializer 和 deserializer 都按预期生效。若需要回退,恢复依赖及构建配置的完整组合,保留能区分两份 mapper 的输入输出用例。