Spring Boot 4 拥抱 Jackson 3:包名迁移、配置改名与自动配置源码剖析

本文是 Spring Boot 4 系列第 08 篇 | 基于 Spring Boot 4.1.0 源码 | 预计阅读 25 分钟

文末附「迁移清单」(必须改 / 可以不动 / 何时移除),升级时可直接对照。


写在前面

从 Spring Boot 3 升到 Spring Boot 4,mvn compile 第一波报错大概率来自 Jackson------几百个 import 全部"包不存在"。原因是 Spring Boot 4 把 JSON 序列化从 Jackson 2 切到了 Jackson 3,运行时类从 com.fasterxml.jackson 搬到了 tools.jackson

这篇基于 Spring Boot 4.1.0 源码,讲清楚三件事:Jackson 3 到底改了什么(包名迁移与注解的"例外")、从 Boot 3 迁移有哪些代码必须改、JacksonAutoConfiguration 被拆进独立模块后是怎么重写的------包括 4.1 新增的 spring.jackson.read/write.*JsonFactoryBuilderCustomizer 扩展点。所有结论都来自 Spring Boot 4.1.0 源码(module/spring-boot-jackson 模块)和官方参考文档。

内容速览

  • 升级后第一个报错长什么样,以及为什么注解不用改
  • Jackson 3 坐标、主类、默认行为的三层变化
  • Boot 3 → Boot 4 迁移:依赖、import、配置项的分步指南
  • JacksonAutoConfiguration 底层重构:Factory → Builder → Mapper 三层装配
  • 4.1 新能力:约束配置、格式无关特性、Factory 定制
  • 迁移清单:必须改 / 可以不动 / 何时移除

一、升级后的第一个报错

假设刚把项目从 Spring Boot 3.3 升到 Spring Boot 4.0,mvn compile 还没跑完就红了:

java 复制代码
// Boot 3 里你写了几百次的 import,全报"包不存在"
import com.fasterxml.jackson.databind.ObjectMapper;        // ❌ 找不到
import com.fasterxml.jackson.databind.json.JsonMapper;      // ❌ 找不到
import com.fasterxml.jackson.databind.Module;               // ❌ 找不到

// 但下面这些却还能用
import com.fasterxml.jackson.annotation.JsonProperty;       // ✅ 依然有效
import com.fasterxml.jackson.annotation.JsonFormat;         // ✅ 依然有效

打开 pom.xml 一看,依赖坐标也变了:

xml 复制代码
<!-- 依赖坐标也变了 -->
<dependency>
    <groupId>tools.jackson.core</groupId>      <!-- 原来: com.fasterxml.jackson.core -->
    <artifactId>jackson-databind</artifactId>  <!-- 原来: jackson-databind -->
</dependency>

这就是 Spring Boot 4 的 Jackson 3 迁移:运行时类全部搬到了 tools.jackson 包,但注解留在 com.fasterxml.jackson.annotation。这么设计是为了让迁移时少改一半代码------注解出现在实体类、DTO 甚至第三方库的字节码里,数量级是百万级的,留在原包名意味着所有注解引用零改动。

背景:Jackson 3.0 由 FasterXML 在 2024 年 10 月正式发布,是一次打破二进制兼容的重写(包名从 com.fasterxml.jackson 迁到 tools.jackson);Spring Framework 7.0 与 Spring Boot 4.0(2025 年 11 月 GA)以 Jackson 3 为主打。Spring Boot 4.1.0 管理的 Jackson 3 版本是 3.1.4


二、Jackson 3 的核心变化全景

2.1 坐标变化:com.fasterxml.jacksontools.jackson(但有一个例外)

Jackson 3 把主构件全部迁移到了新的 group tools.jackson:

构件 Jackson 2 坐标 Jackson 3 坐标
core com.fasterxml.jackson.core:jackson-core tools.jackson.core:jackson-core
databind com.fasterxml.jackson.core:jackson-databind tools.jackson.core:jackson-databind
XML com.fasterxml.jackson.dataformat:jackson-dataformat-xml tools.jackson.dataformat:jackson-dataformat-xml
CBOR com.fasterxml.jackson.dataformat:jackson-dataformat-cbor tools.jackson.dataformat:jackson-dataformat-cbor
注解 com.fasterxml.jackson.core:jackson-annotations com.fasterxml.jackson.core:jackson-annotations(不变!)

注意最后一行:jackson-annotations 是唯一的例外 。它在 Jackson 3 的 BOM 里仍然发布在 com.fasterxml.jackson.core 组下,甚至版本号还是 2.x 线。Spring Boot 4.1.0 的依赖管理里有两行可以互相印证(gradle.properties):

properties 复制代码
jackson2Version=2.21.4   # 保留给还没迁移到 Jackson 3 的第三方库
jacksonVersion=3.1.4     # Jackson 3 主版本

platform/spring-boot-dependencies/build.gradle 中,Jackson 3 BOM 是这样引入的:

groovy 复制代码
library("Jackson Bom", "${jacksonVersion}") {
    group("tools.jackson") {
        bom("jackson-bom") {
            permit("com.fasterxml.jackson.core:jackson-annotations")  // 放行唯一留在 com.fasterxml 的构件
        }
    }
}

解开 Jackson 3.1.4 的 BOM 确认,注解构件的定义是:

xml 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.21</version>   <!-- 注解版本跟随 2.x 线,包名 com.fasterxml.jackson.annotation 不变 -->
</dependency>

@JsonProperty@JsonFormat@JsonInclude 这些注解出现在实体类、DTO 甚至第三方库的字节码里,数量级是百万级的。把它们留在原包名,意味着所有注解引用零改动------升级的代码面直接砍半。这是 Jackson 3 面向迁移友好性最重要的一个决定。

2.2 主类变化:JsonMapper 上位

Jackson 3 中,类名和职责有这些关键调整:

Jackson 2 Jackson 3 说明
com.fasterxml.jackson.databind.ObjectMapper tools.jackson.databind.json.JsonMapper 推荐主类JsonMapper 是 JSON 专属的 ObjectMapper 子类(JsonMapper extends ObjectMapper)。ObjectMapper 类本身仍保留、未被标 @Deprecated,但官方方向是"按格式选 mapper"
com.fasterxml.jackson.databind.Module tools.jackson.databind.JacksonModule 模块抽象改名
com.fasterxml.jackson.core.JsonParser.Feature tools.jackson.core.json.JsonReadFeature + tools.jackson.core.StreamReadFeature 读特性拆分:JSON 专属 + 格式无关两层
com.fasterxml.jackson.core.JsonGenerator.Feature tools.jackson.core.json.JsonWriteFeature + tools.jackson.core.StreamWriteFeature 写特性同理拆分
com.fasterxml.jackson.databind.DatatypeFeature(抽象类) tools.jackson.databind.cfg.DatatypeFeature(接口)+ DateTimeFeature / EnumFeature / JsonNodeFeature 三个实现 特性接口保留,按领域拆成三个枚举实现
PropertyNamingStrategy(抽象类) PropertyNamingStrategy(类,移入 tools.jackson.databind)+ PropertyNamingStrategies(常量宿主类) 结构与 2.12+ 一致:标准实现集中到 PropertyNamingStrategies 的静态常量(如 SNAKE_CASE)
StreamReadConstraints / StreamWriteConstraints 保留(同包名 → tools.jackson.core) 深度/长度/数量等安全约束,Boot 4.1 的 spring.jackson.factory.constraints.* 配置的就是它

有个容易踩的坑:网上很多文章说"ObjectMapper 被废弃了",不准确 。准确的说法是:Jackson 3 鼓励按数据格式使用具体类型(JSON 用 JsonMapper、XML 用 XmlMapper、CBOR 用 CBORMapper),ObjectMapper 类依然存在且可用,只是不再是你应该"点名"的类型。

2.3 默认行为变化:Jackson 3 的默认值变了

Jackson 3 的默认配置与 Jackson 2.x 有几处值得注意的差异,但并非所有差异都会在升级后体现------因为 Boot 2/3 时代 Boot 本身就已经修改了部分默认值:

  • 日期时间 :Jackson 3 默认按 ISO-8601 字符串输出日期/时间(WRITE_DATES_AS_TIMESTAMPS / WRITE_DURATIONS_AS_TIMESTAMPS 默认关闭,2.x 库默认开启)。但 Boot 2.0 起(2017 年提交 "Switch Jackson write-dates-as-timestamps default")就在自动配置里通过 FEATURE_DEFAULTS 默认关闭了 WRITE_DATES_AS_TIMESTAMPS------所以 Boot 3 → Boot 4 日期时间输出没有变化 ,java.util.Date 与 Java 8 时间 API 默认都是 ISO 字符串
  • 字段排序 :Jackson 3 默认按字母序输出属性(SORT_PROPERTIES_ALPHABETICALLY 默认开启,2.x 默认关闭)------这是升级后最容易察觉的行为差异:JSON 字段顺序会变成字母序
  • 未知属性 :Jackson 3 默认忽略 未知属性(FAIL_ON_UNKNOWN_PROPERTIES 默认关闭,与 2.x 相反)------与 Boot 2/3 时代 Boot 强制关闭后的行为一致,没有迁移差异
  • 此外还有 FAIL_ON_EMPTY_BEANS(Jackson 3 默认关闭 → 空 bean 序列化不再报错,Boot 3 中默认报错)、USE_GETTERS_AS_SETTERS(getter 不再充当 setter)等少数默认翻转

升级后真正需要关注的行为差异 集中在:字段排序(字母序开启)、空 bean 序列化不再报错等少数几项;日期时间、未知属性、视图包含等方面 Boot 3 与 Boot 4 默认一致。想整体找回 Boot 3 行为,用 spring.jackson.use-jackson-2-defaults=true(默认 false),它内部调用 configureForJackson2() 把一整批 2.x 默认翻转回来,下文 3.4 节细讲。


三、从 Boot 3 迁移到 Boot 4 的分步指南

3.1 依赖:spring-boot-starter-json 不用动

Boot 4 里 JSON 支持仍然由 spring-boot-starter-json 提供,只是它内部挂载的模块变了(starter/spring-boot-starter-json/build.gradle):

groovy 复制代码
dependencies {
    api(project(":starter:spring-boot-starter"))
    api(project(":module:spring-boot-jackson"))   // 4.0 起 Jackson 自动配置被拆到这个独立模块
}

项目里凡是 spring-boot-starter-web / spring-boot-starter-webflux 的,都传递依赖了这个 starter,依赖层面你什么都不用改

3.2 代码:必须改的 import(清单)

升级时把 IDE 的 import 批量替换一遍即可,规则非常机械:

java 复制代码
// ❌ 删除 / 替换(com.fasterxml.jackson.* 运行时类)
import com.fasterxml.jackson.databind.ObjectMapper;              // → tools.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.json.JsonMapper;          // → tools.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.Module;                   // → tools.jackson.databind.JacksonModule
import com.fasterxml.jackson.databind.DeserializationFeature;   // → tools.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.core.JsonParser;                   // → tools.jackson.core.json.JsonReadFeature 等
import com.fasterxml.jackson.databind.SerializationFeature;     // → tools.jackson.databind.SerializationFeature
import com.fasterxml.jackson.dataformat.xml.XmlMapper;          // → tools.jackson.dataformat.xml.XmlMapper

// ✅ 保持不动(com.fasterxml.jackson.annotation.* 注解)
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

你自己 new 的 ObjectMapper 换成 JsonMapper:

java 复制代码
// Boot 3 时代的写法
// ObjectMapper mapper = new ObjectMapper();
// ObjectMapper mapper = JsonMapper.builder().build();

// Boot 4 的标准写法
tools.jackson.databind.json.JsonMapper mapper = JsonMapper.builder()
        .enable(SerializationFeature.INDENT_OUTPUT)
        .build();

经验法则:包名前缀 com.fasterxml.jackson. 后面跟 annotation 的(注解)不用改,其余都要改成 tools.jackson. 。只有 annotation 这一个例外------这是 Jackson 3 迁移唯一需要记的一条规则。

3.3 配置项:spring.jackson.* 的改名与新增

Boot 4 对 Jackson 配置项做了一次大整理,用官方 metadata 文件(module/spring-boot-jackson/src/main/resources/META-INF/additional-spring-configuration-metadata.json)里的原话:

json 复制代码
{ "name": "spring.jackson.generator", "deprecation": { "level": "error",
  "reason": "Partially replaced by 'spring.jackson.json.write'.", "since": "4.0.0" } },
{ "name": "spring.jackson.parser",    "deprecation": { "level": "error",
  "reason": "Partially replaced by 'spring.jackson.json.read'.",  "since": "4.0.0" } }

spring.jackson.parser.* / spring.jackson.generator.* 自 4.0.0 起标记 error 级废弃,被 spring.jackson.json.read.* / spring.jackson.json.write.* 替代。完整对照如下:

Boot 3 配置项 Boot 4 配置项 特性枚举 引入版本
spring.jackson.parser.* spring.jackson.json.read.<feature> tools.jackson.core.json.JsonReadFeature 4.0
spring.jackson.generator.* spring.jackson.json.write.<feature> tools.jackson.core.json.JsonWriteFeature 4.0
spring.jackson.deserialization.* 不变 DeserializationFeature 4.0
spring.jackson.serialization.* 不变 SerializationFeature 4.0
spring.jackson.mapper.* 不变 MapperFeature 4.0
spring.jackson.datatype.datetime.* 4.0 新增(DatatypeFeature 接口的 DateTimeFeature 实现) DateTimeFeature 4.0
--- spring.jackson.datatype.enum.* EnumFeature 4.0
--- spring.jackson.datatype.json-node.* JsonNodeFeature 4.0
--- spring.jackson.read.<feature> StreamReadFeature(格式无关) 4.1
--- spring.jackson.write.<feature> StreamWriteFeature(格式无关) 4.1
--- spring.jackson.factory.constraints.* StreamRead/WriteConstraints 4.1
--- spring.jackson.cbor.read/write.* CBORReadFeature/CBORWriteFeature 4.0
--- spring.jackson.xml.read/write.* XmlReadFeature/XmlWriteFeature 4.0
--- spring.jackson.use-jackson2-defaults 布尔开关,默认 false 4.0

其中 spring.jackson.read.* / spring.jackson.write.* 是 4.1 新增的"格式无关"层 :它们对应 StreamReadFeature / StreamWriteFeature,同时作用于 JSON、XML、CBOR 三种格式的 token 读取/写入;而 spring.jackson.json.read.* 只对 JSON 生效。这个拆分对应 Jackson 3 把特性分为"格式无关(Stream 层)"和"格式专属(Json/Cbor/Xml 层)"两级的架构。

一个验证过的细节:spring.jackson.read/write.* 两个属性组在 4.1.0 源码中的提交是 2026-01-07 合入(4.0 GA 之后),spring.jackson.factory.constraints.* 及三个 FactoryBuilderCustomizer 是 2026-02-13 合入,且接口 javadoc 明确标注 @since 4.1.0------所以 4.1 新增这个说法是可靠的。

3.4 一个开关找回旧行为:use-jackson-2-defaults

如果服务大量依赖 Boot 3 时代的序列化行为(例如字段保持声明顺序、空 bean 序列化报错),又不想逐条配置,Boot 4 给了官方开关:

yaml 复制代码
spring:
  jackson:
    use-jackson-2-defaults: true   # 默认 false

它做的事在源码里一目了然(JacksonAutoConfigurationAbstractMapperBuilderCustomizer#customize):

java 复制代码
if (this.jacksonProperties.isUseJackson2Defaults()) {
    builder.configureForJackson2()
        .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS, DateTimeFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
        .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
        .disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
}

即:先调用 JsonMapper.BuilderconfigureForJackson2()------这是 Jackson 3 自带的方法,把 builder 切到"更接近 Jackson 2.x 默认配置"。对照 3.1.4 源码,它一次性调整了 16 个特性:启用时间戳输出×2、未知属性报错、FAIL_ON_EMPTY_BEANS、getter 充当 setter 等 8 个 2.x 默认开启项,同时关闭字段字母序排序、参数名探测、枚举按 toString() 读写等 8 个 3.x 新默认。随后 Boot 再显式关掉四个特性(两个时间戳、未知属性报错、视图默认包含)------其中时间戳、未知属性报错、视图默认包含正是 Boot 3 时代通过 FEATURE_DEFAULTS / Jackson2ObjectMapperBuilder 默认关掉的项,从而整体复刻 Boot 3 时代"Jackson 2 默认 + Boot 修正"的最终行为(字段恢复声明顺序、空 bean 恢复报错;日期时间两个时代本就都是 ISO 字符串)。开启后输出行为与 Boot 3 基本一致,迁移期可以无感过渡,之后再逐个特性迁回 Jackson 3 的新默认。

3.5 还没迁移完的代码怎么办:Jackson 2 兼容模块

Boot 4 没有一刀切踢掉 Jackson 2。官方文档《JSON》章节原文:

Support for Jackson 2 is deprecated and will be removed in a future Spring Boot 4.x release. It is provided purely to ease the migration from Jackson 2 to Jackson 3.

具体做法(源码里两个独立模块并行存在):

  • module/spring-boot-jackson:Jackson 3 自动配置(主路径)
  • module/spring-boot-jackson2 :Jackson 2 自动配置(org.springframework.boot.jackson2.* 包,配置前缀 spring.jackson2.*,Jackson2AutoConfiguration 会注册 com.fasterxml.jackson.databind.ObjectMapper bean)

当两个模块都在 classpath 时,通过"偏好"配置指定用谁(spring.http.converters.preferred-json-mapper=jackson2 表示优先 Jackson 2):

yaml 复制代码
spring:
  http:
    converters:
      preferred-json-mapper: jackson2   # 默认 jackson,可选 jackson2

同类偏好项还有 spring.http.codecs.preferred-json-mapper(WebFlux/响应式客户端)、spring.graphql.rsocket.preferred-json-mapperspring.rsocket.preferred-mapperspring.websocket.messaging.preferred-json-mapper

Jackson 2 的支持何时彻底移除?源码注释给出了明确时间线------module/spring-boot-http-converter 中:

java 复制代码
/**
 * @deprecated since 4.0.0 for removal in 4.3.0 in favor of Jackson 3.
 */
@Configuration(proxyBeanMethods = false)
@Deprecated(since = "4.0.0", forRemoval = true)
class Jackson2HttpMessageConvertersConfiguration { ... }

计划在 4.3.0 移除。老代码可以过渡,但别指望一直过渡下去,最好在 4.2/4.3 之前完成迁移。


四、JacksonAutoConfiguration 底层重构全剖析

这一节跟着源码把 Boot 4 的 Jackson 自动配置从头到尾过一遍。它是本系列第 7 篇讲的"模块化自动配置"(自动配置拆分为更小更精确的模块)最典型的案例。

4.1 模块化:自动配置搬进了 spring-boot-jackson

4.0 之前,Jackson 自动配置躺在庞大的 spring-boot-autoconfigure 里。4.0 起被拆到独立模块 module/spring-boot-jackson,包名从 org.springframework.boot.autoconfigure.jackson 变为 org.springframework.boot.jackson.autoconfigure(javadoc 标注 @since 4.0.0),并通过标准的 AutoConfiguration.imports 注册:

shell 复制代码
# module/spring-boot-jackson/src/main/resources/META-INF/spring/
#   org.springframework.boot.autoconfigure.AutoConfiguration.imports
org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration

这个模块的依赖设计也很讲究(build.gradle):核心依赖只有 core:spring-boottools.jackson.core:jackson-databind,spring-web、CBOR、XML 全部是 optional------意味着即使没有 Web 层,也拿得到配置好的 JsonMapper bean;只有引入对应数据格式/Web 组件时相关配置才生效。

4.2 核心 Bean 装配:Factory → Builder → Mapper 三层

JacksonAutoConfiguration(标注 @ConditionalOnClass(JsonMapper.class))的核心装配如下,这是整个重构的骨架:

java 复制代码
@AutoConfiguration
@ConditionalOnClass(JsonMapper.class)
public final class JacksonAutoConfiguration {

    // ① JacksonComponentModule:收集 @JacksonComponent 注解的序列化器/反序列化器
    @Bean
    JacksonComponentModule jsonComponentModule() {
        return new JacksonComponentModule();
    }

    // ② JsonFactory:JsonFactory.builder() + 所有 JsonFactoryBuilderCustomizer(4.1 新增扩展点)
    @Bean
    @ConditionalOnMissingBean
    JsonFactory jsonFactory(List<JsonFactoryBuilderCustomizer> customizers) {
        JsonFactoryBuilder builder = JsonFactory.builder();
        for (JsonFactoryBuilderCustomizer customizer : customizers) {
            customizer.customize(builder);
        }
        return builder.build();
    }

    // ③ JsonMapper.Builder:prototype 作用域!每次注入都拿到全新 Builder
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @ConditionalOnMissingBean
    JsonMapper.Builder jsonMapperBuilder(List<JsonMapperBuilderCustomizer> customizers, JsonFactory jsonFactory) {
        JsonMapper.Builder builder = JsonMapper.builder(jsonFactory);
        for (JsonMapperBuilderCustomizer customizer : customizers) {
            customizer.customize(builder);
        }
        return builder;
    }

    // ④ 最终产物:@Primary JsonMapper(MVC、WebFlux、RestClient 等都依赖它)
    @Bean
    @Primary
    @ConditionalOnMissingBean
    JsonMapper jacksonJsonMapper(JsonMapper.Builder builder) {
        return builder.build();
    }
}

设计要点:

  1. Builder 是 prototype 作用域 :文档明确说"环境配置会应用到自动配置的 JsonMapper.Builder bean,且对任何用这个 Builder 创建的 mapper 生效"。prototype 保证注入 Builder 时总是拿到新的、应用了所有自定义器的实例,而不是共享可变状态。
  2. 三层职责分离 :JsonFactory(token 层,4.1 起可定制)→ JsonMapper.Builder(mapper 层)→ JsonMapper(成品)。4.1 把 Factory 层暴露成配置对象,是因为 StreamReadConstraints 这类安全约束属于 factory 层,原来根本没法通过配置定制。
  3. @ConditionalOnMissingBean + @Primary :自己定义 JsonMapperJsonMapper.Builder bean 会完全接管(此时自动配置的 mapper 消失);官方建议自定义 JsonMapper 时标记 @Primary

4.3 自定义器体系:BuilderCustomizer(4.0)与 FactoryBuilderCustomizer(4.1)

Boot 3 时代通过 Jackson2ObjectMapperBuilderCustomizer 定制 mapper,Boot 4 里它被一组平行接口取代(Mapper 层 3 个 + Factory 层 3 个,共 6 个):

接口 作用于 版本
JsonMapperBuilderCustomizer JsonMapper.Builder(JSON) @since 4.0.0
CborMapperBuilderCustomizer / XmlMapperBuilderCustomizer CBORMapper.Builder / XmlMapper.Builder @since 4.0.0
JsonFactoryBuilderCustomizer JsonFactoryBuilder(JSON) @since 4.1.0
CborFactoryBuilderCustomizer / XmlFactoryBuilderCustomizer CBORFactoryBuilder / XmlFactoryBuilder @since 4.1.0

接口本身只是一个函数式方法(JsonFactoryBuilderCustomizer 全文):

java 复制代码
@FunctionalInterface
public interface JsonFactoryBuilderCustomizer {
    void customize(JsonFactoryBuilder jsonFactoryBuilder);
}

Boot 自己也实现了这些接口------JacksonJsonCustomizerConfiguration 里的 StandardJsonFactoryBuilderCustomizerStandardJsonMapperBuilderCustomizer,它们实现 Orderedorder = 0 。这意味着可以定义 order 为负数/正数的自定义器,在 Boot 默认处理之前/之后插入自己的逻辑。

迁移提示:Boot 3 的 Jackson2ObjectMapperBuilderCustomizer 在 Jackson 3 路径上被 JsonMapperBuilderCustomizer 取代,方法签名从 customize(Jackson2ObjectMapperBuilder) 变成 customize(JsonMapper.Builder),其余用法几乎一致------这是最常见的"改一行类名就能过编译"的迁移。注意 Jackson2ObjectMapperBuilderCustomizer 这个名字并没有消失:它在 Boot 4 的 spring-boot-jackson2 模块里仍为 Jackson 2 保留(org.springframework.boot.jackson2.autoconfigure 包)。

4.4 AbstractMapperBuilderCustomizer:Boot 对 JsonMapper 的完整配置链路

Boot 自己的 StandardJsonMapperBuilderCustomizer 继承 AbstractMapperBuilderCustomizer,其 customize() 是理解"Boot 到底对 mapper 做了什么"的关键(源码节选,按执行顺序):

java 复制代码
protected void customize(B builder) {
    // ① 是否回归 Jackson 2 默认行为(use-jackson-2-defaults,见 3.4 节)
    if (this.jacksonProperties.isUseJackson2Defaults()) {
        builder.configureForJackson2()
            .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS, DateTimeFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
    }
    // ② ServiceLoader 机制自动发现模块(spring.jackson.find-and-add-modules,默认 true)
    if (this.jacksonProperties.isFindAndAddModules()) {
        builder.findAndAddModules(getClass().getClassLoader());
    }
    // ③ spring.jackson.default-property-inclusion(如 non_null)
    Include propertyInclusion = this.jacksonProperties.getDefaultPropertyInclusion();
    if (propertyInclusion != null) {
        builder.changeDefaultPropertyInclusion((handler) -> handler.withValueInclusion(propertyInclusion)
            .withContentInclusion(propertyInclusion));
    }
    // ④ 时区
    if (this.jacksonProperties.getTimeZone() != null) {
        builder.defaultTimeZone(this.jacksonProperties.getTimeZone());
    }
    // ⑤ ★ 关键:无条件挂载 HandlerInstantiator(见 4.5)
    builder.handlerInstantiator(this.handlerInstantiator);
    // ⑥ spring.jackson.visibility.*
    configureVisibility(builder, this.jacksonProperties.getVisibility());
    // ⑦ 各层特性:deserialization / serialization / mapper /
    //    datatype.datetime / datatype.enum / datatype.jsonNode
    configureFeatures(builder, this.jacksonProperties.getDeserialization(), builder::configure);
    configureFeatures(builder, this.jacksonProperties.getSerialization(), builder::configure);
    configureFeatures(builder, this.jacksonProperties.getMapper(), builder::configure);
    configureFeatures(builder, this.jacksonProperties.getDatatype().getDatetime(), builder::configure);
    configureFeatures(builder, this.jacksonProperties.getDatatype().getEnum(), builder::configure);
    configureFeatures(builder, this.jacksonProperties.getDatatype().getJsonNode(), builder::configure);
    // ⑧ 4.1 新增:格式无关 read/write 特性(StreamReadFeature/StreamWriteFeature)
    configureFeatures(builder, this.jacksonProperties.getRead(), builder::configure);
    configureFeatures(builder, this.jacksonProperties.getWrite(), builder::configure);
    // ⑨ 日期格式(类名或 pattern 字符串)、命名策略、模块、Locale、leniency、构造器探测
    configureDateFormat(builder);
    configurePropertyNamingStrategy(builder);
    configureModules(builder);
    configureLocale(builder);
    configureDefaultLeniency(builder);
    configureConstructorDetector(builder);
}

而 JSON 专属的 spring.jackson.json.read/write.*(JsonReadFeature/JsonWriteFeature)在子类 StandardJsonMapperBuilderCustomizer 中追加配置------所以完整的特性生效顺序是:格式无关层(⑦⑧)→ JSON 专属层(json.read/json.write)

configureFeatures 的实现也值得一提------它用 EnumMap 存特性名,利用 relaxed binding 让 spring.jackson.serialization.indent_output=true(官方文档的例子,大小写/分隔符都不敏感)能命中枚举常量 INDENT_OUTPUT:

java 复制代码
protected <T> void configureFeatures(B builder, Map<T, Boolean> features, BiConsumer<T, Boolean> configure) {
    features.forEach((feature, value) -> {
        if (value != null) {
            configure.accept(feature, value);   // feature 枚举值直接传给 builder.configure(...)
        }
    });
}

4.5 SpringBeanHandlerInstantiator:序列化器也能依赖注入

这是 Boot 4 里一个容易被忽略的增强。AbstractMapperBuilderCustomizer 的构造函数里无条件创建了它:

java 复制代码
AbstractMapperBuilderCustomizer(JacksonProperties jacksonProperties, Collection<JacksonModule> modules,
        AutowireCapableBeanFactory beanFactory) {
    ...
    this.handlerInstantiator = new SpringBeanHandlerInstantiator(beanFactory);
}

SpringBeanHandlerInstantiator(类上 javadoc 说明它是 Spring Framework 7 中 org.springframework.http.support.JacksonHandlerInstantiator 的 Boot 内嵌版本,这样 spring-boot-jackson 模块就不必依赖 spring-web )继承 tools.jackson.databind.cfg.HandlerInstantiator,把 Jackson 实例化 handler 的动作全部转交给 Spring 容器:

java 复制代码
@Override
public @Nullable ValueSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated,
        Class<?> serClass) {
    return (ValueSerializer<?>) this.beanFactory.createBean(serClass);   // 走 Spring bean 工厂!
}

效果 :自定义的 ValueSerializer / ValueDeserializer / KeyDeserializer 等,通过 @JsonSerialize(using = XxxSerializer.class) 被 Jackson 按类实例化时,是 Spring 容器创建的 ------构造器参数、@Autowired 字段全部照常注入。配合 Boot 4 的 @JacksonComponent 注解(见 4.6),序列化器里注入一个 Service 从需要额外配置变成了天然支持。

4.6 新特性:@JacksonComponent@JacksonMixin 与模块注册

@JacksonComponent (org.springframework.boot.jackson.JacksonComponent,@since 4.0.0,元标注 @Component):注意这是 Boot 4 的改名版 ------Boot 3 时代的注解叫 @JsonComponent(org.springframework.boot.jackson.JsonComponent),Boot 4 的 Jackson 3 路径改用新名 JacksonComponent,旧名 JsonComponent 只保留在 spring-boot-jackson2 兼容模块(org.springframework.boot.jackson2.JsonComponent)。它声明在 ValueSerializer/ValueDeserializer/KeyDeserializer 实现上,或在包含这些内部类的普通类上。Boot 的 JacksonComponentModule(继承 tools.jackson.databind.module.SimpleModule)会把容器里所有 @JacksonComponent bean 自动注册进 mapper。4.0 还提供了两个便捷基类 ObjectValueSerializer / ObjectValueDeserializer,写"对象包装"类序列化时能省掉一大半样板代码。

@JacksonMixin (org.springframework.boot.jackson.JacksonMixin,@since 4.0.0):Boot 4 新增 。声明在 mixin 类上,Boot 自动扫描应用包(AutoConfigurationPackages),把混入注册到 mapper:

java 复制代码
// 目标类不想动,用 mixin 附加注解
@JacksonMixin(type = User.class)   // 或简写 @JacksonMixin(User.class)
public abstract class UserMixin {
    @JsonProperty("user_name")
    abstract String getName();
}

扫描由 JacksonMixinModuleEntries.scan(context, packages) 完成------内部就是 ClassPathScanningCandidateComponentProvider + AnnotationTypeFilter,然后 JacksonMixinModule 统一注册。它还配套了 AOT 处理器(JacksonMixinModuleEntriesBeanRegistrationAotProcessor,见模块的 aot.factories),原生镜像下也能工作。

JacksonModule bean 自动注册 :任何 tools.jackson.databind.JacksonModule 类型的 bean 都会被 configureModules 收集并 addModules 到 Builder 上;另外 ServiceLoader 机制发现的模块默认也会被 findAndAddModules 拉进来(spring.jackson.find-and-add-modules=false 可关)。

4.7 下游怎么用:消息转换器与 Codecs 的装配链路

自动配置的 JsonMapper 最终通过两个新模块进入 Web 层:

MVC / 命令式客户端 (module/spring-boot-http-converter;Spring Framework 7 把 MappingJackson2HttpMessageConverter 改名为 JacksonJsonHttpMessageConverter,旧类已在 Spring Framework 7.0 标注 @Deprecated,仅保留给 Jackson 2 路径):

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(JsonMapper.class)
@ConditionalOnBean(JsonMapper.class)
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
        havingValue = "jackson", matchIfMissing = true)
static class JacksonJsonHttpMessageConverterConfiguration {

    @Bean
    @Order(0)
    @ConditionalOnMissingBean(...)
    JacksonJsonHttpMessageConvertersCustomizer jacksonJsonHttpMessageConvertersCustomizer(JsonMapper jsonMapper) {
        return new JacksonJsonHttpMessageConvertersCustomizer(jsonMapper);
    }
}

// 自定义器内部:
@Override
public void customize(ServerBuilder builder) {
    builder.withJsonConverter(new JacksonJsonHttpMessageConverter(this.jsonMapper));
}

注意这里的机制已经和 Boot 3 完全不同:Spring Framework 7 的 org.springframework.http.converter.HttpMessageConverters 提供了 ClientBuilder/ServerBuilder 构建器,Boot 4 的 HttpMessageConvertersAutoConfiguration 通过自定义器机制把 Jackson 转换器接进去------JacksonJsonHttpMessageConvertersCustomizer 只是把"用自动配置的 JsonMapper 构造转换器"这件事以自定义器的形式挂上------Spring MVC、RestTemplate、RestClient、@HttpExchange 客户端全都走这一条链路。PREFERRED_MAPPER_PROPERTY 就是 3.5 节说的 spring.http.converters.preferred-json-mapper

WebFlux / 响应式客户端 (module/spring-boot-http-codecCodecsAutoConfiguration,@since 4.0.0):

java 复制代码
return (configurer) -> {
    CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
    defaults.jacksonJsonDecoder(new JacksonJsonDecoder(jsonMapper));
    defaults.jacksonJsonEncoder(new JacksonJsonEncoder(jsonMapper));
};

JacksonJsonEncoder / JacksonJsonDecoder 同样是 Spring Framework 7 给 Jackson2JsonEncoder/Decoder 改的新名字,两者在 Spring Framework 7.0.8 的 org.springframework.http.codec.json 包里并存(后者为 Jackson 2 保留,在 Boot 里对应 Jackson2JsonCodecConfiguration,同样标注 @Deprecated(since = "4.0.0", forRemoval = true))。

4.8 测试支持

JacksonTester(org.springframework.boot.test.json,@since 1.4.0)位置不变,但内部已经持有 JsonMapper 字段;@JsonTest 切片自动配置通过 spring-boot-jackson 模块里专门的 .imports 文件注册(AutoConfigureJson.importsJacksonAutoConfigurationAutoConfigureJsonTesters.importsJacksonTesterTestAutoConfiguration),切片 includes 声明了 JacksonComponentJacksonModule 两类 bean------测试路径基本零迁移成本。


五、4.1 新能力实战:约束、格式无关特性与 Factory 定制

5.1 spring.jackson.factory.constraints.*:防止 JSON 炸弹

Jackson 3 把"深度、长度、数量"这类安全约束收敛到了 StreamReadConstraints / StreamWriteConstraints(tools.jackson.core)。Boot 4.1 把它们暴露为配置,JSON/XML/CBOR 三种工厂统一生效(AbstractFactoryBuilderCustomizer 对三个工厂共用):

java 复制代码
protected void customize(B builder) {
    Constraints constraints = this.jacksonProperties.getFactory().getConstraints();
    builder.streamReadConstraints(readConstraintsFrom(constraints.getRead()));
    builder.streamWriteConstraints(writeConstraintsFrom(constraints.getWrite()));
}

配置方式:

yaml 复制代码
spring:
  jackson:
    factory:
      constraints:
        read:
          max-nesting-depth: 1000      # 最大嵌套深度,默认 500
          max-document-length: 10485760  # 最大文档长度(字节),默认 -1 表示不限
          max-token-count: 1000000     # 最大 token 数,默认 -1 表示不限
          max-number-length: 1000      # 数字最大长度,默认 1000
          max-string-length: 20000000  # 字符串最大长度,默认 100_000_000
          max-name-length: 50000       # 属性名最大长度,默认 50000
        write:
          max-nesting-depth: 1000      # 写入侧嵌套深度,默认 500

对照 Jackson 3.1.4 源码:StreamReadConstraints 的默认值(DEFAULT_MAX_DEPTH=500DEFAULT_MAX_STRING_LEN=100_000_000DEFAULT_MAX_NAME_LEN=50_000 等)与 Boot 这些属性的默认值完全一致------Boot 只是把它们"显性化"了。注意 Jackson 3 的字符串上限从 2.x 的 20MB 提到了 100MB,如果解析超大 JSON 且未配置,行为可能和 Boot 3 时代不一样。

5.2 spring.jackson.read/write.*:格式无关特性

4.1 新增的格式无关层:

yaml 复制代码
spring:
  jackson:
    read:
      use-fast-double-parser: true     # StreamReadFeature:快速浮点解析
    write:
      use-fast-double-writer: true     # StreamWriteFeature
    json:
      read:
        allow-single-quotes: true      # JsonReadFeature(JSON 专属)

JSON 应用场景下它与 spring.jackson.json.read.* 的差别不大,但如果项目同时有 XML 或 CBOR 序列化需求,这一层让"一次配置、处处生效",不用三个格式各写一份。

5.3 自定义 Factory:JsonFactoryBuilderCustomizer

想在 token 层做点配置层面表达不了的事,直接注册自定义器:

java 复制代码
@Configuration(proxyBeanMethods = false)
public class JacksonFactoryConfig {

    @Bean
    JsonFactoryBuilderCustomizer myFactoryCustomizer() {
        return builder -> builder
                .streamReadConstraints(StreamReadConstraints.builder()
                        .maxNestingDepth(2000)
                        .build())
                .enable(JsonReadFeature.ALLOW_JAVA_COMMENTS);   // 容忍 JSON 里的注释
    }
}

由于 Boot 自带 Standard 自定义器 order=0,自定义器若想"在 Boot 处理之后"生效,声明 Ordered 给一个正数即可。


六、迁移清单:必须改 / 可以不动 / 何时移除

必须改(升级即报错)

  • 所有 com.fasterxml.jackson 下的运行时类 import → tools.jackson(只有 annotation 子包不变)
  • new ObjectMapper()JsonMapper.builder().build();依赖 ObjectMapper 的第三方整合代码按库各自迁移
  • com.fasterxml.jackson.databind.Moduletools.jackson.databind.JacksonModule
  • spring.jackson.parser.*spring.jackson.json.read.*;spring.jackson.generator.*spring.jackson.json.write.*
  • Jackson2ObjectMapperBuilderCustomizerJsonMapperBuilderCustomizer(方法参数 Jackson2ObjectMapperBuilderJsonMapper.Builder)
  • 使用 Jackson2ObjectMapperBuilder(Spring Framework 7.0 已标 @Deprecated)的代码改为 JsonMapper.builder() 链式构建
  • @JsonComponent@JacksonComponent(Boot 4 注解改名,包名仍为 org.springframework.boot.jackson;旧注解名 JsonComponent 仅在 spring-boot-jackson2 兼容模块保留)

可以不动

  • @JsonProperty / @JsonFormat / @JsonIgnore / @JsonInclude所有注解 (com.fasterxml.jackson.annotation 原包名原坐标)
  • spring-boot-starter-json 依赖(内部自动切换到 spring-boot-jackson 模块)
  • spring.jackson.deserialization.* / serialization.* / mapper.* 等保留配置项
  • JacksonTester 测试代码(org.springframework.boot.test.json 包不变)
  • 自定义序列化器的基类选择:ObjectValueSerializer/ObjectValueDeserializer(4.0 新增,可选用)

新旧行为差异(行为级,不是编译级)

  • 字段按字母序输出 (Boot 3 不排序)、空 bean 序列化不再报错 (Boot 3 报错)→ 想整体找回 Boot 3 行为用 spring.jackson.use-jackson-2-defaults=true;日期时间(都是 ISO 字符串)与未知属性(都忽略)方面 Boot 3 与 Boot 4 默认一致,无需迁移
  • StreamReadConstraints 字符串上限 20MB → 100MB(Jackson 3 默认)

何时彻底移除

  • Jackson 2 支持:spring-boot-jackson2 模块 + spring.jackson2.* + preferred-json-mapper=jackson2 过渡到 4.3.0 移除 (源码 @Deprecated(since = "4.0.0", forRemoval = true))
  • 官方文档原话:Jackson 2 支持"仅为缓解迁移而存在,不应长期依赖"

七、总结

Spring Boot 4 拥抱 Jackson 3 的本质,是一次"运行时重写、注解不变"的阶梯式迁移:

  1. 坐标层 :tools.jackson 新 group + jackson-annotations 留在 com.fasterxml.jackson 的刻意例外,把迁移成本砍半;
  2. 配置层 :4.0 完成 parser/generatorjson.read/json.write 改名与 use-jackson-2-defaults 开关,4.1 新增格式无关的 spring.jackson.read/write.*spring.jackson.factory.constraints.*;
  3. 源码层 :自动配置拆进 spring-boot-jackson 独立模块,Factory → Builder(prototype) → Mapper(@Primary) 三层装配,自定义器从 Jackson2ObjectMapperBuilderCustomizer 进化出 Builder/Factory 两套接口,HandlerInstantiator 让序列化器也能依赖注入,@JacksonMixin 带来 Boot 4 全新能力;
  4. 兼容层 :spring-boot-jackson2 模块为还没迁移的代码兜底,但 4.3.0 就会移除------现在是迁移的最佳窗口

com.fasterxml 只留下注解这一个例外,运行时已经全面进入 tools.jackson 时代;Boot 4.1 又在这一层之上补全了 factory 定制与格式无关配置,让 Jackson 3 的能力第一次被 Spring Boot 完整地表达出来。

下一篇聊 Spring Boot 4 把容错能力搬进 spring-core 的变化:@Retryable、限流、熔断不再需要第三方库,RetryTemplate 如何在 spring-core 里重生。


相关推荐
用户9385156350713 分钟前
手写一个 LLM Harness 框架:用工程化手段把大模型幻觉踩在脚下
javascript·人工智能·后端
wei_shuo1 小时前
KES 云原生部署与弹性扩展:容器化、Kubernetes编排与自动伸缩
后端
一木之林1 小时前
Python.五.(一)--1. 并发编程、异步IO与多进程
后端
离陌在学C#1 小时前
C# 重载与重写:深入理解面向对象编程的核心概念
java·c#
feng尘1 小时前
# 彻底搞懂 ReentrantLock 与 tryLock:从秒杀实战到 AQS 独占模式源码剖析
后端
啷里格啷1 小时前
Linux进程管理完全指南:从基础到云原生编排
后端·架构
洛阳泰山1 小时前
AI 应用层被 Python 卷成红海,为什么我偏要用 Java 造一个 RAG + 工作流引擎?
java·人工智能·后端
二十雨辰2 小时前
[Java]-Spring面试题
java·开发语言
Wz_z_z_z2 小时前
SpringBoot Actuator 泄露挖掘实战:从 /env 到 heapdump
后端
9i编程2 小时前
工具是编程的铠甲(下篇):从文件对比、全文搜索到数据库设计
后端·openai·ai编程