ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot4迁移:Jackson3包名大变,注解别乱改

SpringBoot4迁移:Jackson3包名大变,注解别乱改 最近把一个跑了三年的 Spring Boot 3.x 项目往 4.x 迁。出发前的预判是最麻烦的应该是 Spring Security或者一堆废弃的 Spring API。结果pom一改、依赖一拉IDE 里最先红掉的是全项目出现次数最多的那行 import——com.fasterxml.jackson.databind.ObjectMapper。查了 Spring Boot 4 的迁移说明才确认不是依赖没拉下来是 Boot 4 默认已经从 Jackson 2 切到了 Jackson 3。而且这不是一次普通的版本升级——Maven GroupId 和 Java Package 一起换了com.fasterxml.jackson大部分变成了tools.jackson。01 第一步把 Codec 类改对而不是只改编译通过项目里 JSON 用得最重的是事件序列化Kafka、Redis、第三方 HTTP API 都走它。老代码长这样JavaOrderEventCodec.javaimport com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;Componentpublic class OrderEventCodec {private final ObjectMapper objectMapper;public OrderEventCodec(ObjectMapper objectMapper) {this.objectMapper objectMapper;}public String encode(OrderCreatedEvent event) {try {return objectMapper.writeValueAsString(event);} catch (JsonProcessingException e) {throw new IllegalStateException(serialize failed, e);}}public OrderCreatedEvent decode(String json) {try {return objectMapper.readValue(json, OrderCreatedEvent.class);} catch (JsonProcessingException e) {throw new IllegalStateException(deserialize failed, e);}}}Boot 3 时代这段代码遍地都是。到了 Boot 4第一反应当然是改 import。但我最后没有继续注入通用ObjectMapper而是换成了更明确的JsonMapper——这也是 Jackson 3 官方推荐的路线JSON 用 JsonMapperYAML 用 YAMLMapperXML 用 XmlMapper别再往一个通用 ObjectMapper 里塞各种 Factory。JavaOrderEventCodec.javaimport tools.jackson.databind.json.JsonMapper;Componentpublic class OrderEventCodec {private final JsonMapper jsonMapper;public OrderEventCodec(JsonMapper jsonMapper) {this.jsonMapper jsonMapper;}public String encode(OrderCreatedEvent event) {return jsonMapper.writeValueAsString(event);}public OrderCreatedEvent decode(String json) {return jsonMapper.readValue(json, OrderCreatedEvent.class);}}这里有两个变化。一个显而易见包名换了。另一个更值得注意——异常体系变了。02 JsonProcessingException 没了异常从 checked 变 uncheckedJackson 2 时代JsonProcessingException几乎是每个序列化方法的固定搭配。因为它继承IOException是 checked 异常编译器逼着你处理。于是几乎所有项目里都长着同一种没有业务意义的 catch——包一层IllegalStateException再抛出去。Jackson 3 把它换成了JacksonException而且基础异常改成了 RuntimeException不再继承 IOException。也就是说升级之后上面那种没有恢复能力的 try/catch 可以直接删掉JavaOrderEventCodec.java// Jackson 3: 不再强制捕获, 没有恢复能力就让异常往外走public String encode(OrderCreatedEvent event) {return jsonMapper.writeValueAsString(event);}真正需要区分“JSON 解析失败”做降级的地方再显式捕获JavaEventConsumer.javaimport tools.jackson.core.JacksonException;try {return jsonMapper.readValue(json, OrderCreatedEvent.class);} catch (JacksonException e) {// 记录原始消息、进死信队列、或者走业务降级throw e;}只是现在不再是编译器强迫你——异常处理回归了它本来的含义只在有能力处理的地方处理。03 最大的坑注解没有全部换包名看到com.fasterxml.jackson → tools.jackson很多人包括第一次迁移的我的第一反应是在 IDE 里全局 Replace。千万别。Jackson 3 有一个刻意的例外jackson-annotations没有搬。JavaUserResponse.java// 这两个 import 在 Boot 4 里保持原样, 不要改!import com.fasterxml.jackson.annotation.JsonIgnore;import com.fasterxml.jackson.annotation.JsonProperty;public record UserResponse(JsonProperty(user_id)Long userId,String nickname,JsonIgnoreString internalRemark) {}而且还有个更细的区分普通的JsonProperty、JsonIgnore、JsonCreator、JsonInclude留在com.fasterxml.jackson.annotation但 databind 层的注解比如JsonSerialize、JsonDeserialize跟着 databind 一起迁了Javaimport-compare.java// 旧import com.fasterxml.jackson.databind.annotation.JsonSerialize;// 新import tools.jackson.databind.annotation.JsonSerialize;我的建议别做全局替换。先让 Maven 完成依赖升级再让 IDE 按编译错误逐类修 import。项目越大这反而越安全——全局替换会把本来正确的注解改坏。04 ObjectMapper 变 immutableset/enable 那套写法过时了老项目里几乎一定有这样一个配置类JavaJacksonConfig.javaConfigurationpublic class JacksonConfig {BeanObjectMapper objectMapper() {ObjectMapper mapper new ObjectMapper();mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);mapper.registerModule(new JavaTimeModule());return mapper;}}这种写法在 Jackson 3 里已经不推荐了——ObjectMapper 改成了 immutable builder 设计。以前“创建实例后不断 set / enable / register”的路线现在应该在 Builder 阶段把配置备齐再一次性 buildJavaJsonMapperFactory.javaJsonMapper jsonMapper JsonMapper.builder().changeDefaultPropertyInclusion(inclusion -inclusion.withValueInclusion(JsonInclude.Include.NON_NULL)).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build();原来的mapper.copy()也没了。想基于现有配置派生一个新 Mapper用rebuild()JavaPrettyPrintTest.java// 测试里需要 pretty 输出时, 从现有 Mapper 派生一个独立实例JsonMapper prettyMapper jsonMapper.rebuild().enable(SerializationFeature.INDENT_OUTPUT).build();我后来把项目里的 Mapper 用法分成两类业务代码统一注入 Boot 自动配置的JsonMapper只有测试、文件导出这类确实需要不同规则的地方才用rebuild()派生独立实例。比满项目new ObjectMapper()好控制得多。05 自定义 ObjectMapper Bean建议直接删掉说实话大部分项目当年定义ObjectMapperBean无非是为了配这几件事忽略 null、日期格式、时区、枚举行为、未知字段、命名策略。这些Spring Boot 的配置文件本身就能配YAMLapplication.ymlspring:jackson:default-property-inclusion: non_nullserialization:write-dates-as-timestamps: false确实需要代码级定制的用 Boot 4 新的JsonMapperBuilderCustomizer——它就是Jackson2ObjectMapperBuilderCustomizer的继任者JavaJacksonConfig.javaimport org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;import tools.jackson.databind.SerializationFeature;Configurationpublic class JacksonConfig {BeanJsonMapperBuilderCustomizer jsonMapperCustomizer() {return builder - builder.disable(SerializationFeature.INDENT_OUTPUT);}}Boot 4 文档专门提醒自己提供一个 JsonMapper Bean会整体替换掉自动配置的 MapperBoot 帮你注册的 Module、Converter 也会一起失效。所以我的选择是删掉自定义 Bean能进配置文件的进配置文件。06 JavaTimeModule 也可以顺手删了还有一段祖传代码估计很多项目里都有Javalegacy-snippet.javaObjectMapper mapper new ObjectMapper();mapper.registerModule(new JavaTimeModule()); // 当年的固定搭配当年不注册它LocalDateTime确实会出问题于是这行成了 ObjectMapper 的固定搭配。Jackson 3 已经把jackson-datatype-jsr310、jackson-datatype-jdk8、jackson-module-parameter-names整合进了 databindPOM 里的依赖和这行registerModule可以一起删了。07 老 SDK 还在 Jackson 2可以共存别硬迁自己的代码能改第三方 SDK 未必。升级途中就会出现这种局面业务代码用的是tools.jackson...JsonMapper某个 SDK 却要求传入com.fasterxml...ObjectMapper——名字一样完全不是同一个类。好在当初改 GroupId 和 package本来就有让 2/3 共存的考虑。过渡期可以加上这个依赖XMLpom.xmldependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-jackson2/artifactId/dependency再配合这个开关让 Jackson 3 的 JsonMapper 尽量沿用 Boot 3 Jackson 2 的默认行为YAMLapplication.ymlspring:jackson:use-jackson2-defaults: true这个开关并不会把 Jackson 3 变回 Jackson 2只是对齐旧行为。我的分阶段做法是第一阶段开着它把代码迁完、接口测试全过第二阶段单独关掉它再跑一轮回归——这样能精确看出哪些 JSON 行为真的变了而不是在一轮大升级里同时排查几十个问题。08 升级回归重点看的不是“能不能启动”纯 REST 项目升级 Jackson最多是前后端字段对不上。但如果 JSON 还参与了这些链路影响就大得多Kafka 历史消息还能不能反序列化Redis 里以前缓存的 JSON 还能不能读数据库 JSON 字段的读写行为第三方接口签名用的 JSON 有没有变化金额精度、日期格式、null 字段输出。尤其是签名这种JavaPaymentClient.javaString body jsonMapper.writeValueAsString(request);String signature sign(body, secret); // JSON 变了, 签名就变了字段顺序、日期格式、null 输出任何一个变了发出去的就是另一串数据对方校验直接失败。这类代码我一定单独加一组升级回归测试而不是只确认项目能编译。09 总结我的迁移顺序先升 Spring Boot 4.xJackson 版本交给 Boot 管理修编译错误package 迁移注意 annotations 是例外 已删除 API删掉自定义 ObjectMapper Bean回到 Boot 自动配置的 JsonMapper删掉 JavaTimeModule 相关依赖和注册代码老 SDK 用 spring-boot-jackson2 use-jackson2-defaults 过渡分两阶段关开关跑接口、Kafka、Redis、第三方协议的 JSON 回归。现在动手其实时机不错Spring Boot 4.1.1 管理的是 Jackson 3.1.5而 3.1 是 Jackson 3.x 的第一条 LTS 分支比刚出 3.0 那会儿稳多了。如果项目还停在 Spring Boot 3.5也该开始看了——3.5.16 已经是 3.5.x 最后一个 OSS 版本。最后提醒一句升级后 IDE 里那行红色的com.fasterxml.jackson.databind.ObjectMapper先别怀疑 Maven——它不是下载失败是陪了 Java 十几年的包名真的换了。如果这篇帮你避开一次升级事故帮忙点个“在看”和“星标”下次升级不踩坑
返回列表