2团
Published on 2025-12-12 / 0 Visits
0
0

Spring Boot 3 → 4 迁移指南:Jackson 2到Jackson 3升级实战

文档目标:为Java开发工程师提供从Spring Boot 3迁移到Spring Boot 4.0.0的完整指南,重点讲解Jackson版本升级的影响、配置变化、API调用差异,以及如何通过兼容层保证平滑过渡。


一、迁移概述

1.1 Spring Boot 4.0.0的核心变化

Spring Boot 4.0.0是一个重大版本升级,其中Jackson模块的主要变化包括:

1.2 升级影响范围

  • ✅ 大部分代码无需修改(Spring Boot自动适配)

  • ⚠️ 自定义Jackson配置需要调整

  • ⚠️ 使用了Jackson内部API的代码需要重构

  • ❌ 依赖Jackson 2的第三方库可能不兼容


二、Jackson 3 的主要变化

2.1 包名和 Maven 坐标变化

Jackson 2.x

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;

Jackson 3.0

<dependency>
    <groupId>tools.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
import tools.jackson.databind.ObjectMapper;  // ← 新包名
import tools.jackson.core.JacksonException;   // ← 新异常类

⚠️ 例外jackson-annotations 保持不变

<!-- 注解模块在Jackson 3 中仍使用旧的 Group ID -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
</dependency>
// 注解包名不变
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;

2.2 核心类重命名

Jackson 2

Jackson 3

说明

JsonFactory

TokenStreamFactory

流式工厂类

JsonProcessingException

JacksonException

根异常类

JsonParseException

StreamReadException

解析异常

JsonGenerationException

StreamWriteException

生成异常

ObjectMapper

JsonMapper (JSON)

JSON 专用 Mapper

-

XmlMapper (XML)

XML 专用 Mapper

-

YAMLMapper (YAML)

YAML 专用 Mapper

2.3 Spring Boot类重命名

Spring Boot 3

Spring Boot 4

说明

@JsonComponent

@JacksonComponent

组件注解

@JsonMixin

@JacksonMixin

Mixin 注解

JsonObjectSerializer

ObjectValueSerializer

序列化器

JsonValueDeserializer

ObjectValueDeserializer

反序列化器

Jackson2ObjectMapperBuilderCustomizer

JsonMapperBuilderCustomizer

Builder 定制器

2.4 异常模型变化(重要!)

Jackson 2.x:使用受检异常(Checked Exception)

// Jackson 2: 必须显式捕获或声明抛出
public String serialize(Object obj) throws JsonProcessingException {
    return objectMapper.writeValueAsString(obj);
}

try {
    String json = objectMapper.writeValueAsString(data);
} catch (JsonProcessingException e) {  // 必须捕获
    log.error("序列化失败", e);
}

Jackson 3.0:使用非受检异常(Unchecked Exception)

// Jackson 3: 可选择是否捕获
public String serialize(Object obj) {  // 不需要throws声明
    return objectMapper.writeValueAsString(obj);
}

// 可以不捕获(让异常向上传播)
String json = objectMapper.writeValueAsString(data);

// 也可以选择捕获
try {
    String json = objectMapper.writeValueAsString(data);
} catch (JacksonException e) {  // 改为JacksonException
    log.error("序列化失败", e);
}

迁移建议

  • ✅ 保留 try-catch 块(向后兼容)

  • ✅ 异常类型从 JsonProcessingException 改为 JacksonException

  • ✅ 移除方法签名中的 throws 声明(可选)

2.5 ObjectMapper 不可变性

Jackson 2.x:ObjectMapper可变

ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);  // 直接修改
mapper.setSerializationInclusion(Include.NON_NULL);         // 直接修改

Jackson 3.0:ObjectMapper不可变,使用Builder模式

// 方式 1: 使用 Builder
JsonMapper mapper = JsonMapper.builder()
    .enable(SerializationFeature.INDENT_OUTPUT)
    .serializationInclusion(Include.NON_NULL)
    .build();

// 方式 2: 从现有 Mapper 复制并修改
JsonMapper newMapper = mapper.rebuild()
    .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
    .build();

2.6 Java 8模块内置

Jackson 2.x:需要单独添加依赖

<!-- Jackson 2: 需要显式添加 -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>  <!-- Java 8 时间 -->
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>    <!-- Optional 等 -->
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>  <!-- 参数名 -->
</dependency>

Jackson 3.0:自动内置

<!-- Jackson 3: 自动包含,无需添加 -->
<!-- 以下功能已内置到 jackson-databind -->
<!-- - java.time 支持 (LocalDateTime, Instant 等) -->
<!-- - Optional 支持 (Optional<T>, OptionalInt 等) -->
<!-- - 构造函数参数名自动识别 -->

三、从Spring Boot 3到4的完整迁移步骤

3.1 步骤1:升级Spring Boot版本

<!-- pom.xml -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.0</version>  <!-- 从 3.x 升级到 4.0.0 -->
</parent>

3.2 步骤2:更新Import语句

如果您的代码直接使用了Jackson API:

// ❌ Spring Boot 3 / Jackson 2
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;

// ✅ Spring Boot 4 / Jackson 3
import tools.jackson.databind.ObjectMapper;     // 改包名
import tools.jackson.core.JacksonException;     // 改异常类
import tools.jackson.core.type.TypeReference;

// ✅ 注解包名不变
import com.fasterxml.jackson.annotation.*;  // 保持不变

3.3 步骤3:更新异常处理

// ❌ Jackson 2风格(受检异常)
public String toJson(Object obj) throws JsonProcessingException {
    try {
        return objectMapper.writeValueAsString(obj);
    } catch (JsonProcessingException e) {
        log.error("序列化失败", e);
        throw e;
    }
}

// ✅ Jackson 3风格(非受检异常)
public String toJson(Object obj) {  // 不需要throws声明
    try {
        return objectMapper.writeValueAsString(obj);
    } catch (JacksonException e) {  // 改为 JacksonException
        log.error("序列化失败", e);
        throw e;  // 或者转换为业务异常
    }
}

3.4 步骤4:更新ObjectMapper配置

// ❌ Jackson 2风格(直接修改)
@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    mapper.registerModule(new JavaTimeModule());  // Java 8时间模块
    return mapper;
}

// ✅ Jackson 3风格(Builder 模式)
@Bean
public JsonMapper jsonMapper() {  // 使用JsonMapper
    return JsonMapper.builder()
        .enable(SerializationFeature.INDENT_OUTPUT)
        .serializationInclusion(JsonInclude.Include.NON_NULL)
        // Java 8模块已内置,无需注册
        .build();
}

3.5 步骤5:更新Spring Boot类注解

// ❌ Spring Boot 3
@JsonComponent
public class CustomSerializer extends JsonSerializer<MyObject> {
    // ...
}

// ✅ Spring Boot 4
@JacksonComponent  // 改名
public class CustomSerializer extends JsonSerializer<MyObject> {
    // ...
}

3.6 步骤6:更新配置文件

# ❌ Spring Boot 3
spring:
  jackson:
    read:
      enum-as-empty-string-lenient: true
    write:
      dates-as-timestamps: false

# ✅ Spring Boot 4
spring:
  jackson:
    json:
      read:  # 新增 json 层级
        enum-as-empty-string-lenient: true
      write:  # 新增 json 层级
        dates-as-timestamps: false

四、Jackson 2兼容性配置(过渡方案)

4.1 场景说明

当您的项目包含以下情况时,需要保留 Jackson 2 支持:

  • 使用了依赖 Jackson 2 的第三方库(如Spring Cloud Gateway WebMVC)

  • 暂时无法完成代码重构

  • 需要渐进式迁移

4.2 Maven依赖配置

在需要 Jackson 2 兼容的模块中添加:

<dependencies>
    <!-- 必需:Jackson 2兼容模块 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-jackson2</artifactId>
    </dependency>
    
    <!-- 示例:需要Jackson 2的第三方库 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway-server-webmvc</artifactId>
    </dependency>
</dependencies>

重要说明:Jackson 2(com.fasterxml.jackson.*)和Jackson 3(tools.jackson.*)的包名不同(仅jackson-annotations保持一致),这意味着两个版本可以共存。但需要注意:

  • 优点:不会出现同名类冲突,可以在同一项目中同时使用两个版本

  • ⚠️ 风险:如果不小心混用(如Jackson 2的ObjectMapper处理Jackson 3的注解),会导致运行时错误

  • 📦 包体积:同时引入两个版本会增加约3-4MB的依赖体积

最佳实践:尽量避免长期共存,仅在过渡期使用兼容方案。


五、性能对比:Jackson 2 vs Jackson 3(JMH基准测试)

本节通过JMH(Java Microbenchmark Harness)基准测试,展示Jackson 2与Jackson 3的性能差异。

5.1 简化的 JMH 测试代码示例

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 3, time = 2)
@Measurement(iterations = 5, time = 3)
@Fork(2)
@Threads(4)
public class Jackson2vs3Benchmark {
    
    private ObjectMapper jackson2Mapper;
    private JsonMapper jackson3DefaultMapper;
    private JsonMapper jackson3TunedMapper;
    private TestData testData;
    
    @Setup
    public void setup() {
        // Jackson 2
        jackson2Mapper = new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        
        // Jackson 3 默认配置
        jackson3DefaultMapper = JsonMapper.builder()
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .build();
        
        // Jackson 3 优化配置(使用 ThreadLocal 回收池)
        jackson3TunedMapper = JsonMapper.builder(
            JsonFactory.builder()
                .recyclerPool(JsonRecyclerPools.threadLocalPool())
                .build()
        ).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build();
        
        testData = new TestData(/* 测试数据 */);
    }
    
    @Benchmark
    public String jackson2_serialize() throws Exception {
        return jackson2Mapper.writeValueAsString(testData);
    }
    
    @Benchmark
    public String jackson3_default_serialize() throws Exception {
        return jackson3DefaultMapper.writeValueAsString(testData);
    }
    
    @Benchmark
    public String jackson3_tuned_serialize() throws Exception {
        return jackson3TunedMapper.writeValueAsString(testData);
    }
}

5.2 实测性能数据

测试环境:JDK 25,WSL2,4线程,-Xms2G -Xmx2G,测试对象包含 UUID、LocalDateTime、Optional、List 和嵌套对象。

Benchmark                                    Mode  Cnt     Score      Error   Units
==================================================================================
jackson2_serialize                          thrpt   10  7297.855 ± 474.856  ops/ms
jackson2_deserialize                        thrpt   10  2431.000 ±  67.187  ops/ms

jackson3_default_serialize                  thrpt   10  4631.573 ± 548.454  ops/ms
jackson3_default_deserialize                thrpt   10  2280.646 ±  49.615  ops/ms

jackson3_threadLocal_serialize              thrpt   10  6614.307 ± 468.045  ops/ms
jackson3_threadLocal_deserialize            thrpt   10  2350.067 ± 141.227  ops/ms

5.3 性能对比分析

测试场景

Jackson 2

Jackson 3 (默认)

Jackson 3 (优化)

性能差异说明

序列化

7297.855

4631.573 (63.5%)

6614.307 (90.6%)

默认配置性能下降 36.5%,优化后恢复至 90.6%

反序列化

2431.000

2280.646 (93.8%)

2350.067 (96.7%)

性能影响较小,优化后接近Jackson 2

关键结论

  1. ⚠️ Jackson 3 默认配置使用deque-based RecyclerPool,导致序列化性能下降约 36.5%

  2. 启用threadLocalPool 后,性能可恢复到Jackson 2的 90-97%

  3. 📊 反序列化性能受影响较小(约6%),无论是否优化差异都不大

  4. 🎯 对于高并发/低延迟服务,强烈建议使用优化配置

5.4 性能优化配置示例

import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.util.JsonRecyclerPools;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {
    
    @Bean
    public JsonMapper jsonMapper() {
        // 使用 threadLocalPool 获得接近 Jackson 2 的性能
        JsonFactory factory = JsonFactory.builder()
            .recyclerPool(JsonRecyclerPools.threadLocalPool())
            .build();
        
        return JsonMapper.builder(factory).build();
    }
}

六、总结

6.1 迁移检查清单

在完成 Jackson 2 到 Jackson 3 的迁移后,请确认以下检查点:

代码层面

  • [ ] 已更新所有import语句(com.fasterxml..jackson.*tools.jackson.*

  • [ ] 异常处理已从JsonProcessingException改为JacksonException

  • [ ] ObjectMapper配置改用Builder模式(JsonMapper.builder()

  • [ ] Spring Boot注解已更新(@JsonComponent@JacksonComponent

  • [ ] 配置文件YAML层级已调整(增加 json: 层级)

依赖管理

  • [ ] Spring Boot版本已升级到 4.0.0

  • [ ] 如需兼容Jackson 2,已添加spring-boot-jackson2依赖

  • [ ] 移除了手动添加的Java 8相关模块依赖(已内置)


Comment