本文是 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.jackson → tools.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
它做的事在源码里一目了然(JacksonAutoConfiguration 的 AbstractMapperBuilderCustomizer#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.Builder 的 configureForJackson2()------这是 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.ObjectMapperbean)
当两个模块都在 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-mapper、spring.rsocket.preferred-mapper、spring.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-boot 和 tools.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();
}
}
设计要点:
- Builder 是 prototype 作用域 :文档明确说"环境配置会应用到自动配置的
JsonMapper.Builderbean,且对任何用这个 Builder 创建的 mapper 生效"。prototype 保证注入 Builder 时总是拿到新的、应用了所有自定义器的实例,而不是共享可变状态。 - 三层职责分离 :
JsonFactory(token 层,4.1 起可定制)→JsonMapper.Builder(mapper 层)→JsonMapper(成品)。4.1 把 Factory 层暴露成配置对象,是因为StreamReadConstraints这类安全约束属于 factory 层,原来根本没法通过配置定制。 @ConditionalOnMissingBean+@Primary:自己定义JsonMapper或JsonMapper.Builderbean 会完全接管(此时自动配置的 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 里的 StandardJsonFactoryBuilderCustomizer 和 StandardJsonMapperBuilderCustomizer,它们实现 Ordered、order = 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-codec 的 CodecsAutoConfiguration,@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.imports → JacksonAutoConfiguration、AutoConfigureJsonTesters.imports → JacksonTesterTestAutoConfiguration),切片 includes 声明了 JacksonComponent 与 JacksonModule 两类 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=500、DEFAULT_MAX_STRING_LEN=100_000_000、DEFAULT_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.Module→tools.jackson.databind.JacksonModule -
spring.jackson.parser.*→spring.jackson.json.read.*;spring.jackson.generator.*→spring.jackson.json.write.* -
Jackson2ObjectMapperBuilderCustomizer→JsonMapperBuilderCustomizer(方法参数Jackson2ObjectMapperBuilder→JsonMapper.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 的本质,是一次"运行时重写、注解不变"的阶梯式迁移:
- 坐标层 :
tools.jackson新 group +jackson-annotations留在com.fasterxml.jackson的刻意例外,把迁移成本砍半; - 配置层 :4.0 完成
parser/generator→json.read/json.write改名与use-jackson-2-defaults开关,4.1 新增格式无关的spring.jackson.read/write.*和spring.jackson.factory.constraints.*; - 源码层 :自动配置拆进
spring-boot-jackson独立模块,Factory → Builder(prototype) → Mapper(@Primary)三层装配,自定义器从Jackson2ObjectMapperBuilderCustomizer进化出 Builder/Factory 两套接口,HandlerInstantiator让序列化器也能依赖注入,@JacksonMixin带来 Boot 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 里重生。