文章目录

SmartOA 后端升级到 Spring Boot 4 后,Jackson 也迎来了大版本跃迁:Jackson 2.x → 3.x(tools.jackson)。包名从 com.fasterxml.jackson 换成 tools.jackson,序列化器/反序列化器/ObjectMapper 全部要改。后端 1797 个 Java 文件里,受影响的就有 405 个。这是一次典型的「升级连带改造」,记录完整的手术过程和依赖升级纪律。

一、为什么动 Jackson

Spring Boot 4 的默认 JSON 序列化已经切换到 Jackson 3(tools.jackson),旧包 com.fasterxml.jackson.databind 在新框架下成了遗留依赖。如果继续用 Jackson 2,会出现两个 ObjectMapper 并存、注解不生效等诡异问题。既然要上 Spring Boot 4,Jackson 3 就必须同步迁。

💡 升级纪律:补丁版本直接升;次大版本(x.0→x.1)先出风险评估报告(影响面、代码量、工作量),确认后再动手。Jackson 2→3 属于「框架强制要求」的大版本迁移,风险集中在包名替换和自定义序列化器兼容性。

二、迁移前:盘点影响面

先做全量扫描,摸清两类改动点:

# 1. 包名引用:com.fasterxml.jackson → tools.jackson
grep -rl "com.fasterxml.jackson" backend/ | wc -l
# 输出:405 个文件

# 2. 自定义序列化器/反序列化器(逻辑可能不兼容,需逐个看)
grep -rln "extends JsonSerializer\|extends JsonDeserializer" backend/

405 个文件里,绝大多数是机械的 import 替换;真正需要人工判断的是自定义序列化器/反序列化器和 ApiEncrypt 加解密链路。

三、批量替换:405 个文件的 import

包名替换用 IDE 全局替换 + 正则收尾,替换规则:

旧包新包
com.fasterxml.jackson.databindtools.jackson.databind
com.fasterxml.jackson.coretools.jackson.core
com.fasterxml.jackson.annotationtools.jackson.annotation

替换后立即 mvn compile,编译器会帮你找出漏网之鱼。注意:不是所有 com.fasterxml 都无脑替换——项目里如果还有第三方库内部依赖 Jackson 2,它们的类路径不能动,只改自己代码里的 import。

四、真正的坑:自定义序列化器与 ApiEncrypt

机械替换之后,编译过了,但测试开始报错。问题集中在三类:

坑 1:序列化器/反序列化器 API 变化

Jackson 3 里 JsonSerializer.serialize() 的签名有变化(Generator 参数类型调整),自定义序列化器需要适配:

// Jackson 2 时代
public class XxxSerializer extends JsonSerializer<Xxx> {
    @Override
    public void serialize(Xxx value, JsonGenerator gen, SerializerProvider p) {
        gen.writeString(value.getCode());
    }
}

// Jackson 3(tools.jackson):Generator 类型与异常声明调整
public class XxxSerializer extends JsonSerializer<Xxx> {
    @Override
    public void serialize(Xxx value, JsonGenerator gen, SerializerProvider p)
            throws IOException {
        gen.writeString(value.getCode());
    }
}

坑 2:ObjectMapper 注入方式

项目里多处 @Autowired ObjectMapper 或自己 new 的 ObjectMapper,Jackson 3 的 ObjectMapper 构建方式变了(builder 模式更彻底):

// ❌ 旧习惯:new ObjectMapper() + 手动注册模块
var mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());

// ✅ Jackson 3:JsonMapper.builder() 链式构建
var mapper = JsonMapper.builder()
        .addModule(new JavaTimeModule())
        .build();

坑 3:ApiEncrypt 加解密链路

接口加解密依赖自定义序列化器在出参时加密、入参时解密。Jackson 3 迁移后,反序列化器的读取逻辑和异常类型都变了,解密失败从静默返回空串改为抛 RuntimeException(M3 修复)——这反而是个改进:以前解密失败静默返回空串,等于把「解密失败」伪装成「数据为空」,绕过密码校验;现在直接抛异常,问题立刻暴露。

🚨 教训:安全相关的「静默失败」都是隐患。解密失败必须抛异常,不能让错误数据继续往下走。

五、顺带清理:405 个文件的重复 import

迁移过程中发现大量文件存在重复 import(历史遗留),mvn compile 不报但代码很脏。这次一并清理:405 个 Java 文件去重。用 Spotless 统一格式化兜底,保证后续 diff 干净。

六、连带修复:Druid 双重初始化 + BouncyCastle 重复注册

升级期间顺手修了两个严重 bug 风险:

这两个都属于「平时不炸、升级才炸」的定时炸弹,大版本升级是最好的排查窗口。

七、迁移清单(可直接复用)

  1. 先盘点再动手:grep 统计影响文件数(405),区分「机械替换」和「人工适配」
  2. 包名替换走 IDE:全局替换 + 编译器兜底,别手改
  3. 自定义序列化器逐个看:签名变化 + 异常声明,不能只看编译过没过
  4. ObjectMapper 构建方式:builder 模式,模块注册方式同步改
  5. 安全链路优先测:加解密、脱敏这类序列化相关功能写回归测试,升级后先跑
  6. 顺手清理:重复 import、旧 API 调用,升级窗口期一并处理
✅ 最终成果:Jackson 3 全量迁移完成,405 个文件全部适配,自定义序列化器/反序列化器/ApiEncrypt 链路测试通过,Druid 与 BouncyCastle 隐患一并修复。

评 论