最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring?Boot4實(shí)戰(zhàn)指南之Jackson2.x升級(jí)到3.x踩坑完全記錄

 更新時(shí)間:2026年05月06日 08:29:02   作者:葉落閣主  
Spring?Boot升級(jí)幾乎是Java后端項(xiàng)目繞不開的話題,尤其是很多老項(xiàng)目還停留在2.x,一旦要升級(jí),就會(huì)發(fā)現(xiàn)牽扯的絕不只是改個(gè)版本號(hào),這篇文章主要介紹了Spring?Boot4實(shí)戰(zhàn)指南之Jackson2.x升級(jí)到3.x踩坑完全記錄的相關(guān)資料,需要的朋友可以參考下

前言

最近在將公司的 Spring Cloud 微服務(wù)項(xiàng)目從 Spring Boot 3.2 + JDK 17 升級(jí)到 Spring Boot 4.0 + JDK 21 的過程中,Jackson 從 2.x 到 3.x 的升級(jí)是改動(dòng)最大、坑最多的部分之一。

Jackson 3.0.0 于 2025 年 10 月 3 日正式發(fā)布,這是自 2013 年 Jackson 2.0 以來的首個(gè)大版本更新,經(jīng)歷了近 8 年的開發(fā)。Spring Boot 4.0 也正式將 Jackson 3 作為默認(rèn) JSON 庫(kù)。

本文將結(jié)合實(shí)際項(xiàng)目代碼,從官方變更說明真實(shí)踩坑經(jīng)驗(yàn)兩個(gè)維度,帶大家完整走一遍 Jackson 2.x → 3.x 的遷移過程。

一、Jackson 3.x 的核心變化概覽

在動(dòng)手之前,我們先從官方文檔梳理 Jackson 3.x 相比 2.x 的核心變化:

1.1 包名和 Maven 坐標(biāo)全面更換

這是最直觀的 Breaking Change:

維度Jackson 2.xJackson 3.x
Maven GroupIdcom.fasterxml.jacksontools.jackson
Java 包名com.fasterxml.jackson.*tools.jackson.*
注解包名com.fasterxml.jackson.annotation不變(仍用 2.x 的注解)

特別注意jackson-annotations 是個(gè)例外!Jackson 3.x 依賴的是 jackson-annotations 2.20 版本,注解的包名 com.fasterxml.jackson.annotation 保持不變。這意味著 @JsonFormat、@JsonInclude@JsonProperty 等注解的 import 不需要改。

jackson-databind 中定義的注解(如 @JsonSerialize、@JsonDeserialize會(huì)跟隨包名遷移到 tools.jackson.databind.annotation。

1.2 核心類重命名

Jackson 3.x 對(duì)大量核心類進(jìn)行了重命名,目的是讓命名更加語義化:

Jackson 2.xJackson 3.x說明
ObjectMapperJsonMapper(推薦)ObjectMapper 仍存在,但推薦使用格式特定子類
JsonSerializer<T>ValueSerializer<T>序列化器基類
JsonDeserializer<T>ValueDeserializer<T>反序列化器基類
SerializerProviderSerializationContext序列化上下文
DeserializationContextDeserializationContext不變
JsonProcessingExceptionJacksonException基礎(chǔ)異常類
JsonMappingExceptionDatabindException數(shù)據(jù)綁定異常
JsonParseExceptionStreamReadException解析異常
ModuleJacksonModule避免與 java.lang.Module 沖突
TextNodeStringNodeJsonNode 子類型

1.3 異常體系變?yōu)?Unchecked

這是一個(gè)重大改變:Jackson 3.x 的所有異常都繼承自 RuntimeException,而不是 2.x 中的 IOException。

// Jackson 2.x
JsonProcessingException extends IOException  // checked

// Jackson 3.x
JacksonException extends RuntimeException    // unchecked

這意味著你不再需要在方法簽名中聲明 throws 子句,但 catch 塊中的異常類型需要更新。

1.4 ObjectMapper 變?yōu)椴豢勺?/h3>

Jackson 3.x 中 ObjectMapper(及其子類型如 JsonMapper)是完全不可變的,必須通過 Builder 模式構(gòu)建:

// Jackson 2.x - 可變配置
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.registerModule(new JavaTimeModule());

// Jackson 3.x - Builder 模式,不可變
JsonMapper mapper = JsonMapper.builder()
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
    .changeDefaultPropertyInclusion(old ->
        JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL))
    .addModule(new SomeModule())
    .build();

1.5 Java 8 時(shí)間模塊內(nèi)置

Jackson 2.x 中需要單獨(dú)引入 jackson-datatype-jsr310 來支持 java.time 類型,3.x 已將其內(nèi)置到 jackson-databind,無需額外依賴和注冊(cè):

  • jackson-module-parameter-names → 內(nèi)置
  • jackson-datatype-jdk8(Optional 等)→ 內(nèi)置
  • jackson-datatype-jsr310(java.time)→ 內(nèi)置

1.6 默認(rèn)配置變更

Jackson 3.x 修改了多個(gè) Feature 的默認(rèn)值,這些變更可能是升級(jí)后行為不一致的主因:

Feature2.x 默認(rèn)值3.x 默認(rèn)值影響
FAIL_ON_UNKNOWN_PROPERTIEStruefalse反序列化時(shí)遇到未知字段不再報(bào)錯(cuò)
WRITE_DATES_AS_TIMESTAMPStruefalse日期默認(rèn)輸出 ISO-8601 字符串而非時(shí)間戳
SORT_PROPERTIES_ALPHABETICALLYfalsetrue屬性按字母排序序列化
FAIL_ON_TRAILING_TOKENSfalsetrue解析后檢查是否有多余內(nèi)容
FAIL_ON_NULL_FOR_PRIMITIVESfalsetruenull 賦值給基本類型會(huì)報(bào)錯(cuò)
FAIL_ON_EMPTY_BEANStruefalse空 Bean 序列化不再報(bào)錯(cuò)

二、Spring Boot 4 中的 Jackson 3 集成方式

Spring Boot 4 對(duì) Jackson 的集成方式有重大變化,理解這些變化是順利遷移的關(guān)鍵。

2.1 告別 Jackson2ObjectMapperBuilder

在 Spring Boot 3.x(Jackson 2.x)中,我們通常通過 Jackson2ObjectMapperBuilderCustomizer 來定制 ObjectMapper

@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
    return builder -> builder.indentOutput(true);
}

在 Spring Boot 4.x(Jackson 3.x)中,Spring 不再提供 Jackson2ObjectMapperBuilder 的等價(jià)物,而是直接使用 Jackson 原生的 Builder,通過 JsonMapperBuilderCustomizer 進(jìn)行定制:

@Bean
JsonMapperBuilderCustomizer jacksonCustomizer() {
    return builder -> builder.enable(SerializationFeature.INDENT_OUTPUT);
}

2.2 Jackson 2 和 3 可以共存

得益于包名完全不同(com.fasterxml.jackson vs tools.jackson),Jackson 2 和 3 可以在同一個(gè) classpath 上共存。這為漸進(jìn)式遷移提供了極大便利。

Spring Boot 4 同時(shí)提供了兩套版本的依賴管理,Spring Data Redis 等組件目前仍依賴 Jackson 2.x 的 API,兩套 Jackson 通過不同包名共存互不沖突。

三、實(shí)戰(zhàn)遷移:逐個(gè)擊破

下面結(jié)合我們項(xiàng)目的真實(shí)代碼,逐一講解遷移過程中的關(guān)鍵改動(dòng)。

3.1 JacksonConfig:從 ObjectMapper Bean 到 JsonMapperBuilderCustomizer

這是改動(dòng)最大的一個(gè)文件。我們?cè)瓉硗ㄟ^ @Bean 直接創(chuàng)建 ObjectMapper 實(shí)例:

遷移前(Jackson 2.x):

@Configuration
@AutoConfigureBefore(JacksonAutoConfiguration.class)
public class JacksonConfig {

    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
    private String pattern;

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.registerModule(javaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.setTimeZone(TimeZone.getTimeZone(timezone));
        return objectMapper;
    }

    private Module javaTimeModule() {
        JavaTimeModule module = new JavaTimeModule();
        module.addSerializer(LocalDateTime.class, getDateSerializer(pattern));
        module.addDeserializer(LocalDateTime.class, getDateDeserializer(LocalDateTime.class));
        // ... 其他類型
        return module;
    }

    private <T> JsonSerializer<T> getDateSerializer(String pattern) {
        return new JsonSerializer<>() {
            @Override
            public void serialize(T date, JsonGenerator gen, SerializerProvider provider)
                    throws IOException {
                // 序列化邏輯
            }
        };
    }
}

遷移后(Jackson 3.x):

@Configuration
public class JacksonConfig {

    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
    private String pattern;

    @Value("${spring.jackson.time-zone:}")
    private String timezone;

    @Bean
    public JsonMapperBuilderCustomizer mossJacksonCustomizer() {
        return builder -> {
            String tz = StrUtil.emptyToDefault(timezone, ZoneId.systemDefault().getId());

            SimpleModule module = new SimpleModule("MossDateTimeModule");

            // 序列化
            module.addSerializer(LocalDateTime.class, createDateSerializer(pattern));
            module.addSerializer(LocalDate.class, createDateSerializer(DatePattern.NORM_DATE_PATTERN));
            // ... 其他類型

            // 反序列化
            module.addDeserializer(LocalDateTime.class, createDateDeserializer(LocalDateTime.class));
            module.addDeserializer(LocalDate.class, createDateDeserializer(LocalDate.class));
            // ... 其他類型

            builder.addModule(module);
            builder.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            builder.changeDefaultPropertyInclusion(old ->
                JsonInclude.Value.construct(
                    JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));
            builder.defaultTimeZone(TimeZone.getTimeZone(tz));
        };
    }

    static <T> ValueSerializer<T> createDateSerializer(String pattern) {
        return new ValueSerializer<T>() {
            @Override
            public void serialize(T date, JsonGenerator gen,
                    SerializationContext ctx) throws JacksonException {
                // 序列化邏輯不變,只是參數(shù)類型改了
            }
        };
    }

    static <T> ValueDeserializer<T> createDateDeserializer(Class<T> clazz) {
        return new ValueDeserializer<T>() {
            @Override
            public T deserialize(JsonParser parser,
                    DeserializationContext ctx) throws JacksonException {
                String text = parser.getString(); // getText() -> getString()
                // 反序列化邏輯不變
            }
        };
    }
}

關(guān)鍵變化總結(jié):

變化點(diǎn)Jackson 2.xJackson 3.x
配置方式直接創(chuàng)建 ObjectMapper Bean通過 JsonMapperBuilderCustomizer 定制
注解@AutoConfigureBefore不再需要(Customizer 自動(dòng)生效)
序列化器JsonSerializer<T>ValueSerializer<T>
反序列化器JsonDeserializer<T>ValueDeserializer<T>
序列化上下文SerializerProviderSerializationContext
異常類型IOExceptionJacksonException(unchecked)
獲取文本parser.getText()parser.getString()
時(shí)間模塊JavaTimeModule(需手動(dòng)注冊(cè))SimpleModule(內(nèi)置 jsr310 支持)
序列化包含策略mapper.setSerializationInclusion(...)builder.changeDefaultPropertyInclusion(...)

3.2 網(wǎng)關(guān)層:ObjectMapper 替換為 JsonMapper

在 Gateway 模塊中,之前到處 new ObjectMapper() 來做 JSON 序列化,這在 3.x 中需要改為注入 JsonMapper

遷移前:

public class MossGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // ... 
        ObjectMapper objectMapper = new ObjectMapper(); // 每次新建一個(gè)!
        try {
            return response.writeWith(Mono.just(response.bufferFactory()
                .wrap(objectMapper.writeValueAsBytes(ResultData.fail(...)))));
        } catch (JsonProcessingException ex) {
            log.error("對(duì)象輸出異常:", ex);
        }
    }
}

遷移后:

public class MossGlobalFilter implements GlobalFilter {

    @Resource
    private JsonMapper jsonMapper; // 注入不可變的 JsonMapper

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // ...
        try {
            return response.writeWith(Mono.just(response.bufferFactory()
                .wrap(jsonMapper.writeValueAsBytes(ResultData.fail(...)))));
        } catch (JacksonException ex) {  // 異常類型變了
            log.error("對(duì)象輸出異常:", ex);
        }
    }
}

改動(dòng)點(diǎn):

  1. ObjectMapperJsonMapper(通過 @Resource 注入,不再每次 new)
  2. JsonProcessingExceptionJacksonException
  3. com.fasterxml.jacksontools.jackson 的 import 替換

3.3 Redis 序列化:Jackson 2.x 和 3.x 共存的典型場(chǎng)景

這是一個(gè)非常典型的兩套 Jackson 共存場(chǎng)景。Spring Data Redis 4.0.x 的 Jackson2JsonRedisSerializer 仍基于 Jackson 2.x API,因此 Redis 配置類必須繼續(xù)使用 Jackson 2.x

/**
 * Redis 配置類
 * 
 * 注意:雖然項(xiàng)目已將 REST 序列化遷移到 Jackson 3.x(tools.jackson.*),
 * 但 Spring Data Redis 4.0.x 的 Jackson2JsonRedisSerializer 仍依賴
 * Jackson 2.x(com.fasterxml.jackson.*),因此本類必須繼續(xù)使用 Jackson 2.x API。
 * 兩套 Jackson 通過不同包名在 classpath 上共存,互不沖突。
 */
@Configuration
public class RedisConfig {

    public void commonSerializerConfig(RedisTemplate<String, Object> redisTemplate) {
        // 這里使用的是 com.fasterxml.jackson.databind.ObjectMapper
        // 不是 tools.jackson.databind.json.JsonMapper!
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL);
        om.registerModule(new JavaTimeModule());

        Jackson2JsonRedisSerializer<Object> serializer =
            new Jackson2JsonRedisSerializer<>(om, Object.class);
        redisTemplate.setValueSerializer(serializer);
    }
}

Jackson2JsonRedisSerializer 已被標(biāo)記為 @Deprecated(forRemoval = true),待 Spring Data Redis 升級(jí)適配 Jackson 3.x 后,應(yīng)遷移至新的序列化方案。

3.4 Gateway 異常處理器:包名遷移

// 遷移前
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;

public class CustomErrorWebExceptionHandler implements ErrorWebExceptionHandler {
    private final ObjectMapper objectMapper;
    // ...
    try {
        return response.writeWith(Mono.just(response.bufferFactory()
            .wrap(objectMapper.writeValueAsBytes(...))));
    } catch (JsonProcessingException e) { ... }
}

// 遷移后
import tools.jackson.core.JacksonException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.webflux.error.ErrorWebExceptionHandler;  // 包名也變了!

public class CustomErrorWebExceptionHandler implements ErrorWebExceptionHandler {
    private final JsonMapper jsonMapper;
    // ...
    try {
        return response.writeWith(Mono.just(response.bufferFactory()
            .wrap(jsonMapper.writeValueAsBytes(...))));
    } catch (JacksonException e) { ... }
}

注意不僅 Jackson 的包名變了,Spring Boot 4 自身的一些類的包名也發(fā)生了變化,比如 ErrorWebExceptionHandlerorg.springframework.boot.web.reactive.error 移到了 org.springframework.boot.webflux.error。

四、遷移清單:import 替換速查表

以下是實(shí)際項(xiàng)目中涉及的 import 替換對(duì)照表,可以直接用 IDE 的全局替換功能批量處理:

# Jackson 核心類
com.fasterxml.jackson.core.JsonGenerator        → tools.jackson.core.JsonGenerator
com.fasterxml.jackson.core.JsonParser            → tools.jackson.core.JsonParser
com.fasterxml.jackson.core.JsonProcessingException → tools.jackson.core.JacksonException
com.fasterxml.jackson.databind.ObjectMapper      → tools.jackson.databind.json.JsonMapper
com.fasterxml.jackson.databind.JsonSerializer    → tools.jackson.databind.ValueSerializer
com.fasterxml.jackson.databind.JsonDeserializer  → tools.jackson.databind.ValueDeserializer
com.fasterxml.jackson.databind.SerializerProvider → tools.jackson.databind.SerializationContext
com.fasterxml.jackson.databind.DeserializationContext → tools.jackson.databind.DeserializationContext
com.fasterxml.jackson.databind.DeserializationFeature → tools.jackson.databind.DeserializationFeature
com.fasterxml.jackson.databind.SerializationFeature → tools.jackson.databind.SerializationFeature
com.fasterxml.jackson.databind.Module            → tools.jackson.databind.JacksonModule
com.fasterxml.jackson.databind.module.SimpleModule → tools.jackson.databind.module.SimpleModule

# 時(shí)間模塊(已內(nèi)置,通??梢詣h除 import)
com.fasterxml.jackson.datatype.jsr310.JavaTimeModule → 已內(nèi)置,無需單獨(dú)引入

# 注解(不變?。?
com.fasterxml.jackson.annotation.JsonInclude     → 不變
com.fasterxml.jackson.annotation.JsonFormat       → 不變
com.fasterxml.jackson.annotation.JsonProperty     → 不變

五、踩坑記錄

坑 1:JavaTimeModule 還是 SimpleModule?

Jackson 3.x 已將 jackson-datatype-jsr310 內(nèi)置到 jackson-databind 中,因此 JavaTimeModule 不再作為獨(dú)立模塊存在于 tools.jackson 包下。

如果你之前通過 JavaTimeModule 注冊(cè)自定義的日期序列化/反序列化器,需要改為使用 SimpleModule

// 2.x
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(LocalDateTime.class, customSerializer);

// 3.x
SimpleModule module = new SimpleModule("CustomDateTimeModule");
module.addSerializer(LocalDateTime.class, customSerializer);

坑 2:setSerializationInclusion 被移除

ObjectMapper.setSerializationInclusion() 在 3.x 中被移除,需要通過 Builder 的 changeDefaultPropertyInclusion 方法替代:

// 2.x
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);

// 3.x
builder.changeDefaultPropertyInclusion(old ->
    JsonInclude.Value.construct(
        JsonInclude.Include.NON_NULL,
        JsonInclude.Include.NON_NULL));

坑 3:getText() → getString()

JsonParser.getText() 在 3.x 中被重命名為 getString()。如果你有自定義的反序列化器,IDE 可能不會(huì)自動(dòng)提示這個(gè)變化:

// 2.x
String text = jsonParser.getText();

// 3.x
String text = jsonParser.getString();

坑 4:Redis 序列化器必須用 Jackson 2.x

Spring Data Redis 4.0 的 Jackson2JsonRedisSerializer 構(gòu)造器只接受 com.fasterxml.jackson.databind.ObjectMapper,不兼容 Jackson 3.x 的 tools.jackson.databind.json.JsonMapper。

如果你在 RedisConfig 中也試圖遷移到 Jackson 3.x 的 JsonMapper,會(huì)遇到編譯錯(cuò)誤。正確做法是 Redis 配置繼續(xù)使用 Jackson 2.x,等待 Spring Data Redis 后續(xù)版本的適配。

坑 5:FAIL_ON_UNKNOWN_PROPERTIES 默認(rèn)值變化

Jackson 3.x 中 FAIL_ON_UNKNOWN_PROPERTIES 的默認(rèn)值改為了 false,這實(shí)際上和大多數(shù)項(xiàng)目的期望一致。但如果你之前依賴這個(gè)特性來發(fā)現(xiàn)字段拼寫錯(cuò)誤,升級(jí)后可能會(huì)靜默忽略錯(cuò)誤的字段名。

如果你的項(xiàng)目原本就顯式配置了 false(大多數(shù)項(xiàng)目都是),那么這行配置可以刪掉了:

// 3.x 中這行可以刪除,因?yàn)槟J(rèn)就是 false
builder.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

坑 6:WRITE_DATES_AS_TIMESTAMPS 默認(rèn)值變化

這個(gè)影響范圍很大。Jackson 2.x 默認(rèn)將日期序列化為時(shí)間戳數(shù)字,3.x 改為輸出 ISO-8601 字符串。如果你有大量依賴時(shí)間戳格式的前端代碼或單元測(cè)試,需要特別注意。

不過對(duì)于我們的項(xiàng)目來說,由于本身就通過自定義序列化器輸出 yyyy-MM-dd HH:mm:ss 格式,這個(gè)默認(rèn)值變化沒有實(shí)際影響。

六、單元測(cè)試驗(yàn)證

遷移完成后,編寫覆蓋性的單元測(cè)試非常重要。我們?yōu)?JacksonConfig 編寫了完整的測(cè)試用例,驗(yàn)證所有日期類型的序列化/反序列化:

@Slf4j
public class JacksonConfigTest {

    private JsonMapper buildMapper() {
        SimpleModule module = new SimpleModule("MossDateTimeModule");
        module.addSerializer(LocalDateTime.class,
            JacksonConfig.createDateSerializer("yyyy-MM-dd HH:mm:ss"));
        module.addDeserializer(LocalDateTime.class,
            JacksonConfig.createDateDeserializer(LocalDateTime.class));
        // ... 注冊(cè)其他類型

        return JsonMapper.builder()
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .changeDefaultPropertyInclusion(old ->
                JsonInclude.Value.construct(
                    JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL))
            .defaultTimeZone(TimeZone.getDefault())
            .addModule(module)
            .build();
    }

    @Test
    public void testSerialize_LocalDateTime() throws Exception {
        JsonMapper mapper = buildMapper();
        DateTimeDto dto = new DateTimeDto();
        dto.setLocalDateTime(LocalDateTime.of(2026, 4, 27, 15, 30, 0));

        String json = mapper.writeValueAsString(dto);

        assertTrue(json.contains("2026-04-27 15:30:00"));
        assertFalse(json.contains("2026-04-27T15:30")); // 確認(rèn)沒有 ISO 的 T 分隔符
    }

    @Test
    public void testSerialize_nonNull() throws Exception {
        JsonMapper mapper = buildMapper();
        DateTimeDto dto = new DateTimeDto();
        dto.setTitle("僅標(biāo)題");

        String json = mapper.writeValueAsString(dto);

        assertTrue(json.contains("僅標(biāo)題"));
        assertFalse(json.contains("localDateTime")); // null 字段不應(yīng)出現(xiàn)
    }
}

重點(diǎn)測(cè)試項(xiàng):

  • LocalDateTime / LocalDate / LocalTime 的序列化格式是否符合預(yù)期
  • Date / Timestamp / java.sql.Date 等傳統(tǒng)日期類型是否兼容
  • 時(shí)區(qū)轉(zhuǎn)換邏輯是否正確(服務(wù)端時(shí)區(qū) ↔ 用戶時(shí)區(qū))
  • NON_NULL 策略是否生效
  • @JsonProperty(access = READ_ONLY) 注解是否正常工作

七、遷移步驟總結(jié)

整理一份可操作的遷移步驟清單:

Step 1:升級(jí)依賴

Spring Boot 4.0 會(huì)自動(dòng)管理 Jackson 3.x 的版本,通常不需要顯式指定 Jackson 版本。

Step 2:全局替換 import

# 批量替換包名(注意排除 annotation 包)
find . -name "*.java" -exec sed -i '' \
  's/com\.fasterxml\.jackson\.core/tools.jackson.core/g' {} +
find . -name "*.java" -exec sed -i '' \
  's/com\.fasterxml\.jackson\.databind/tools.jackson.databind/g' {} +

注意:不要替換 com.fasterxml.jackson.annotation,它保持不變。

Step 3:替換核心類名

  • ObjectMapperJsonMapper
  • JsonSerializerValueSerializer
  • JsonDeserializerValueDeserializer
  • SerializerProviderSerializationContext
  • JsonProcessingExceptionJacksonException

Step 4:改造配置類

  • @Bean ObjectMapper 改為 @Bean JsonMapperBuilderCustomizer
  • 刪除 @AutoConfigureBefore(JacksonAutoConfiguration.class)
  • JavaTimeModule 改為 SimpleModule

Step 5:處理共存場(chǎng)景

  • Redis、第三方 SDK 等仍使用 Jackson 2.x 的組件,保持原有代碼不變
  • 添加注釋說明共存原因,方便后續(xù)遷移

Step 6:運(yùn)行測(cè)試并修復(fù)

  • 重點(diǎn)關(guān)注日期格式、屬性排序、null 值處理等行為變化
  • 檢查 SORT_PROPERTIES_ALPHABETICALLY 默認(rèn)值變化對(duì) JSON 對(duì)比測(cè)試的影響

八、參考資料

總結(jié)

Jackson 2.x → 3.x 的升級(jí)雖然是 Breaking Change,但整體遷移路徑比較清晰:

  1. 包名替換是工作量最大但最機(jī)械的部分,可以批量處理
  2. 核心類重命名需要理解新舊對(duì)照關(guān)系,但命名更加直觀
  3. 配置方式變更(Builder 模式)是設(shè)計(jì)理念的升級(jí),代碼更加安全
  4. 默認(rèn)值變化需要逐一檢查對(duì)業(yè)務(wù)的影響
  5. Jackson 2/3 共存讓漸進(jìn)式遷移成為可能

Jackson 3 在安全性、API 設(shè)計(jì)、默認(rèn)配置和性能上都有明顯提升,建議在升級(jí) Spring Boot 4.0 時(shí)同步完成遷移。

到此這篇關(guān)于Spring Boot4實(shí)戰(zhàn)指南之Jackson2.x升級(jí)到3.x踩坑的文章就介紹到這了,更多相關(guān)Jackson2.x升級(jí)到3.x踩坑內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java位掩碼控制權(quán)限與(&)或(|)非(~)、>的介紹

    Java位掩碼控制權(quán)限與(&)或(|)非(~)、>的介紹

    今天小編就為大家分享一篇關(guān)于Java位掩碼控制權(quán)限與(&)或(|)非(~)、>的介紹,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • redis分布式鎖的原理及代碼實(shí)例

    redis分布式鎖的原理及代碼實(shí)例

    這篇文章主要介紹了redis分布式鎖的原理及代碼實(shí)例,Redis作為一款高性能內(nèi)存數(shù)據(jù)庫(kù),其提供了一種非常實(shí)用的分布式鎖解決方案,可以幫助開發(fā)人員輕松地實(shí)現(xiàn)分布式鎖功能,對(duì)于分布式系統(tǒng)的開發(fā)和維護(hù),具有非常大的實(shí)用價(jià)值,需要的朋友可以參考下
    2024-01-01
  • 詳解idea maven nexus 常見命令配置

    詳解idea maven nexus 常見命令配置

    這篇文章主要介紹了idea maven nexus 常見命令配置的相關(guān)知識(shí),通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • 使用Springboot搭建OAuth2.0 Server的方法示例

    使用Springboot搭建OAuth2.0 Server的方法示例

    這篇文章主要介紹了使用Springboot搭建OAuth2.0 Server的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-08-08
  • Java設(shè)計(jì)模式--代理模式

    Java設(shè)計(jì)模式--代理模式

    代理就是一個(gè)人或者一個(gè)機(jī)構(gòu)代表另一個(gè)人或者另一個(gè)機(jī)構(gòu)采取行動(dòng)。在一些情況下,一個(gè)客戶不想或者不能夠直接引用一個(gè)對(duì)象,而代理對(duì)象可以在客戶端和目標(biāo)對(duì)象之前起到中介的作用
    2021-07-07
  • Java注解(Annotations)的定義和使用詳解

    Java注解(Annotations)的定義和使用詳解

    Java注解(Annotations)是Java5引入的一種元數(shù)據(jù)(Metadata),它提供了一種在源代碼中嵌入補(bǔ)充信息的方式,這些信息可以被編譯器、JVM或其他工具在編譯時(shí)、運(yùn)行時(shí)進(jìn)行處理,注解本身不會(huì)直接影響程序的執(zhí)行,但可以用來指導(dǎo)編譯器、JVM或其他工具的行為,從而實(shí)現(xiàn)各種功能
    2025-03-03
  • 最新評(píng)論

    南投市| 林口县| 社旗县| 九江市| 贵阳市| 越西县| 萝北县| 荆州市| 田林县| 融水| 含山县| 天柱县| 通榆县| 方城县| 齐河县| 麻城市| 正阳县| 绥棱县| 安徽省| 亳州市| 稷山县| 白朗县| 通道| 阿巴嘎旗| 瓮安县| 阜宁县| 枞阳县| 塔河县| 靖远县| 石河子市| 双峰县| 灵宝市| 宝清县| 武隆县| 津市市| 台北县| 宜丰县| 虹口区| 永福县| 建阳市| 武宣县|