精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

SpringBoot中這樣用ObjectMapper,才夠優雅!

開發 前端
不是 ObjectMapper 難用,是咱沒 get 到它在 SpringBoot 里的 “優雅姿勢”。今天就跟大家嘮嘮,怎么把 ObjectMapper 用得順風順水,既不用在業務代碼里堆一堆轉換邏輯,又能避免那些讓人頭大的 bug,看完這篇,保準你想把之前的代碼重構一遍(狗頭)。

兄弟們,咱誰沒跟 ObjectMapper 打過交道啊?每次跟 JSON 打交道,不是日期格式突然冒出個 “T” 讓前端小姐姐追著問,就是 null 值莫名消失被測試懟 “接口漏字段”,更絕的是 —— 明明字段名對著呢,反序列化完字段全是 null,當時真想把鍵盤拍在桌上喊 “這玩意兒咋不按套路出牌!”

其實啊,不是 ObjectMapper 難用,是咱沒 get 到它在 SpringBoot 里的 “優雅姿勢”。今天就跟大家嘮嘮,怎么把 ObjectMapper 用得順風順水,既不用在業務代碼里堆一堆轉換邏輯,又能避免那些讓人頭大的 bug,看完這篇,保準你想把之前的代碼重構一遍(狗頭)。

一、別再瞎 new ObjectMapper 了!SpringBoot 早幫你安排了

先問大家一個問題:你是不是寫過這樣的代碼?

// 是不是你?
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(user);
User user = objectMapper.readValue(json, User.class);

要是你點頭了,那咱得先糾正這個小習慣 ——別再每次用都 new ObjectMapper 了!SpringBoot 早就幫咱們做了自動配置,在JacksonAutoConfiguration里,已經默認創建了一個 ObjectMapper 實例,還幫咱們配了不少基礎參數。你直接用@Autowired注入就行,既不用自己管理生命周期,還能跟 SpringBoot 的其他組件(比如消息轉換器、接口返回值處理)無縫銜接。

不信你看,咱隨便寫個 Service:

@Service
public class UserService {
    // 直接注入,不用自己new!
    private final ObjectMapper objectMapper;
    // 構造器注入,Spring推薦姿勢
    public UserService(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
    public String getUserJson(User user) throws JsonProcessingException {
        // 直接用,省心!
        return objectMapper.writeValueAsString(user);
    }
}

有人可能會問:“我自己 new,想配啥配啥,不行嗎?”還真不行!你自己 new 的 ObjectMapper,跟 SpringBoot 默認的不是一個實例。比如你在application.yml里配了全局日期格式,自己 new 的那個根本讀不到這個配置,到時候就會出現 “我明明配了啊,怎么沒生效” 的迷惑行為。

更坑的是,要是你在 Controller 里返回對象,SpringBoot 會用它自己的 ObjectMapper 來序列化,你自己 new 的那個配置完全沒用,等于白忙活。所以聽我的,先把 “Autowired 注入 ObjectMapper” 這個習慣養成,咱再談后續優化。

二、日期處理:從 “T 亂入” 到 “格式自由”

日期處理絕對是 ObjectMapper 的 “重災區”,沒有之一。

上次我同事小王寫了個用戶列表接口,返回的日期是2024-05-20T13:14:00.000+08:00,前端小姐姐拿著截圖來找他:“王哥,這日期里的‘T’是啥意思啊?是要我備注‘今天適合表白’嗎?” 小王當場社死,后來查了半小時才知道,這是 ObjectMapper 默認的日期格式 ——ISO 8601 標準,但前端根本不認這個 “T”,還得轉成yyyy-MM-dd HH:mm:ss才行。

2.1 全局配置:一招解決所有日期格式問題

最優雅的方式,就是在application.yml(或application.properties)里配全局日期格式,這樣所有用 SpringBoot 默認 ObjectMapper 序列化的日期,都會按這個格式來,不用在每個字段上寫注解。

# application.yml
spring:
  jackson:
    # 日期格式:全局統一成 yyyy-MM-dd HH:mm:ss
    date-format: yyyy-MM-dd HH:mm:ss
    # 時區:必須配!不然會有8小時時差
    time-zone: GMT+8
    # 針對LocalDateTime等JDK8新日期類型的配置
    deserialization:
      adjust-dates-to-context-time-zone: false

這里有個坑必須提醒大家:時區一定要配! 要是沒配time-zone: GMT+8,ObjectMapper 會默認用 UTC 時區,結果就是返回的日期比實際少 8 小時,比如你本地是 2024-05-20 13:14,序列化后變成 2024-05-20 05:14,到時候前端以為你接口返回的是昨天的數據,能把你懟到懷疑人生。要是你用的是 JDK8 的新日期類型(LocalDateTime、LocalDate),光配date-format還不夠,因為date-format是針對java.util.Date的。這時候得加個依賴,讓 Jackson 支持 JDK8 日期類型:

<!-- pom.xml 加這個依賴 -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <!-- SpringBoot父工程已經管理了版本,不用自己寫version -->
</dependency>

加了依賴后,再在application.yml里配 JDK8 日期的格式:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    # 配置LocalDateTime的格式
    Java-time-module:
      date-time-formatter: yyyy-MM-dd HH:mm:ss

這樣不管是Date還是LocalDateTime,序列化后都是yyyy-MM-dd HH:mm:ss,前端再也不用問你 “T 是啥” 了。

2.2 局部調整:個別字段要特殊格式怎么辦?

有時候全局格式是yyyy-MM-dd HH:mm:ss,但某個字段需要yyyy-MM-dd(比如用戶的生日,不用時分秒),這時候用@JsonFormat注解就能搞定,局部配置會覆蓋全局配置,非常靈活。

public class User {
    private Long id;
    private String userName;
    // 生日只需要日期,局部配置格式
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate birthday;
    // 注冊時間用全局格式,但這里可以顯式指定(可選)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime registerTime;
    // getter/setter 省略
}

這里要注意:@JsonFormat里的timezone也得寫,不然會繼承全局的時區,但要是你局部格式沒寫時分秒,時區配置不影響結果,不過寫上更穩妥,避免踩坑。

三、null 值處理:留還是刪?別讓前端跟你吵架

另一個高頻痛點:null 值消失。

比如你返回的 User 對象里,nickName是 null,序列化后 JSON 里直接沒這個字段了,前端拿到數據一看:“哎?nickName 呢?你接口漏字段了吧!” 你查代碼發現字段明明在,就是值為 null,這時候才反應過來 ——ObjectMapper 默認會忽略 null 值。

3.1 全局配置:決定 null 值要不要顯示

想讓所有 null 值都顯示在 JSON 里,直接在application.yml里配:

spring:
  jackson:
    # 序列化時包含所有字段,包括null值
    serialization:
      include-null-map-values: true
    # 更直接的配置:所有null值都包含
    default-property-inclusion: ALWAYS

default-property-inclusion有四個可選值,咱解釋一下:

  • ALWAYS:不管是不是 null,都包含字段(最常用)
  • NON_NULL:忽略 null 值(默認)
  • NON_EMPTY:忽略 null、空字符串("")、空集合(比如 [])
  • NON_DEFAULT:忽略字段值等于默認值的(比如 int 字段 0,boolean 字段 false)

比如你想忽略空字符串和空集合,但保留 null 值,就配NON_EMPTY:

spring:
  jackson:
    default-property-inclusion: NON_EMPTY

這樣一來,nickName: null會顯示,address: ""(空字符串)和hobbies: [](空集合)會被忽略,很靈活。

3.2 局部控制:個別字段特殊處理

要是全局配置是ALWAYS(顯示所有 null),但某個字段是 null 時不想顯示,比如password字段(用戶沒傳的話,null 值沒必要返回),用@JsonInclude注解就行:

public class User {
    private Long id;
    privateString userName;

    // 要是password是null,序列化時忽略這個字段
    @JsonInclude(JsonInclude.Include.NON_NULL)
    privateString password;

    // 要是nickName是空字符串或null,都忽略
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    privateString nickName;

    // getter/setter 省略
}

@JsonInclude的取值和全局配置的default-property-inclusion對應,局部配置會覆蓋全局,比如全局是ALWAYS,但password用了NON_NULL,那password為 null 時就會被忽略,其他字段還是顯示 null。這里插個小技巧:要是你想讓某個字段 “永遠顯示”,哪怕是 null,就用@JsonInclude(JsonInclude.Include.ALWAYS),不管全局怎么配,這個字段都會顯示,適合那些前端必須拿到的字段(比如userId,哪怕是 null,前端也要知道這個字段存在)。

四、字段名映射:camelCase 和 snake_case 的 “和解方案”

Java 里我們習慣用駝峰命名(camelCase),比如userName、registerTime,但前端有時候用下劃線命名(snake_case),比如user_name、register_time,這時候反序列化就會出問題 —— 前端傳user_name,你用userName接收,結果userName是 null,因為字段名對不上。

以前我見過有人這么解決:在每個字段上寫@JsonProperty注解,指定下劃線的字段名:

public class User {
    @JsonProperty("user_id")
    private Long userId;

    @JsonProperty("user_name")
    private String userName;

    @JsonProperty("register_time")
    private LocalDateTime registerTime;

    // getter/setter 省略
}

這么寫能解決問題,但字段多了的話,每個都要加注解,手都酸了,而且容易漏寫。其實 SpringBoot 里配個全局字段命名策略,就能讓 camelCase 自動轉 snake_case,不用寫一個注解。

4.1 全局配置:駝峰自動轉下劃線

在application.yml里加一行配置:

spring:
  jackson:
    # 字段命名策略:駝峰轉下劃線(snake_case)
    property-naming-strategy: SNAKE_CASE

這樣一來,你 Java 類里的userName,序列化后會變成user_name;前端傳user_name,反序列化時也會自動映射到userName,完美!除了SNAKE_CASE,還有其他命名策略,比如:

  • CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES:和SNAKE_CASE一樣,駝峰轉下劃線(老版本的寫法,現在推薦用SNAKE_CASE)
  • PASCAL_CASE_TO_CAMEL_CASE:帕斯卡命名(首字母大寫,比如 UserName)轉駝峰(userName)
  • LOWER_CASE:所有字母小寫,比如userName轉username

比如你前端用帕斯卡命名(UserName),就配PASCAL_CASE_TO_CAMEL_CASE,Java 里的userName會自動和前端的UserName映射。

4.2 局部特殊:個別字段不按全局規則來

要是全局是SNAKE_CASE,但某個字段需要特殊命名,比如userId要映射到user_id,但phoneNumber要映射到mobile(不是phone_number),這時候用@JsonProperty注解覆蓋全局規則:

public class User {
    // 全局是SNAKE_CASE,這里會自動轉user_id,不用寫@JsonProperty
    private Long userId;

    // 特殊情況:phoneNumber要映射到mobile,用@JsonProperty指定
    @JsonProperty("mobile")
    private String phoneNumber;

    // getter/setter 省略
}

這樣既保留了全局的便捷性,又能處理特殊字段,優雅!

五、復雜類型:List變 Map?TypeReference 救場

處理 List、Map 這些復雜類型時,很多人會踩一個坑:反序列化 List 的時候,拿到的不是List<User>,而是List<LinkedHashMap>,遍歷的時候強轉User直接報錯。

比如你寫了這樣的代碼:

// 前端傳的JSON數組
String userJson = "[{\"user_name\":\"張三\",\"age\":20},{\"user_name\":\"李四\",\"age\":22}]";

// 想反序列化成List<User>
List<User> userList = objectMapper.readValue(userJson, List.class);

// 遍歷的時候強轉,報錯!
for (User user : userList) { // ClassCastException: LinkedHashMap cannot be cast to User
    System.out.println(user.getUserName());
}

為啥會這樣?因為 Java 的 “泛型擦除”—— 編譯的時候List<User>會變成List,ObjectMapper 不知道你要反序列化成User對象,就默認轉成LinkedHashMap(JSON 對象轉 Map)。這時候就得用TypeReference來告訴 ObjectMapper:“我要的是List<User>,不是普通的 List!”

5.1 用 TypeReference 處理 List

正確的寫法是這樣的:

String userJson = "[{\"user_name\":\"張三\",\"age\":20},{\"user_name\":\"李四\",\"age\":22}]";

// 用TypeReference指定泛型類型
List<User> userList = objectMapper.readValue(userJson, new TypeReference<List<User>>() {});

// 遍歷,沒問題!
for (User user : userList) {
    System.out.println(user.getUserName()); // 正常輸出:張三、李四
}

TypeReference是 Jackson 提供的一個抽象類,通過匿名內部類的方式,保留了泛型的具體類型(因為匿名內部類會在編譯時生成 class 文件,泛型信息不會被擦除),ObjectMapper 就能知道要轉成List<User>了。

5.2 處理嵌套泛型:比如 Map<String, List>

要是更復雜一點,比如 JSON 是{"male":[{"user_name":"張三"}], "female":[{"user_name":"李四"}]},想轉成Map<String, List<User>>,同樣用TypeReference:

String json = "{\"male\":[{\"user_name\":\"張三\",\"age\":20}], \"female\":[{\"user_name\":\"李四\",\"age\":22}]}";

// 嵌套泛型也能搞定
Map<String, List<User>> genderMap = objectMapper.readValue(json, new TypeReference<Map<String, List<User>>>() {});

// 取值
List<User> maleUsers = genderMap.get("male");
System.out.println(maleUsers.get(0).getUserName()); // 張三

這里要注意:TypeReference的匿名內部類不能復用,比如你不能寫個public class UserListTypeReference extends TypeReference<List<User>> {}然后反復用,雖然能跑,但可能會有線程安全問題(Jackson 官方不推薦),最好每次用的時候都 new 一個匿名內部類,雖然代碼看起來重復,但安全第一。

六、自定義序列化:讓性別 1→“男”,不用再寫 if-else

有時候我們需要對字段做特殊轉換,比如數據庫里存的性別是 1(男)、2(女),但接口要返回 “男”、“女”;或者金額存的是分(比如 1000 分 = 10 元),接口要返回元(10.00 元)。

要是在業務代碼里寫 if-else 轉換,比如:

// 不優雅的寫法:業務代碼里混著格式轉換
public UserVO convert(User user) {
    UserVO vo = new UserVO();
    vo.setUserName(user.getUserName());
    // 性別轉換:1→男,2→女
    if (user.getGender() == 1) {
        vo.setGender("男");
    } elseif (user.getGender() == 2) {
        vo.setGender("女");
    } else {
        vo.setGender("未知");
    }
    // 金額轉換:分→元
    vo.setBalance(user.getBalance() / 100.00);
    return vo;
}

這樣寫能實現功能,但業務代碼和格式轉換混在一起,要是有多個地方需要轉換,就會寫一堆重復代碼,維護起來麻煩。這時候用 ObjectMapper 的自定義序列化器,就能把轉換邏輯抽離出來,一勞永逸。

6.1 寫個自定義序列化器:處理性別轉換

首先,寫一個序列化器,繼承StdSerializer,重寫serialize方法:

// 性別序列化器:Integer(1/2)→ String(男/女)
publicclass GenderSerializer extends StdSerializer<Integer> {

    // 必須寫無參構造器,不然Jackson會報錯
    public GenderSerializer() {
        this(null);
    }

    protected GenderSerializer(Class<Integer> t) {
        super(t);
    }

    // 核心方法:轉換邏輯
    @Override
    public void serialize(Integer gender, JsonGenerator gen, SerializerProvider provider) throws IOException {
        // 轉換邏輯:1→男,2→女,其他→未知
        String genderStr = switch (gender) {
            case1 -> "男";
            case2 -> "女";
            default -> "未知";
        };
        // 把轉換后的值寫入JSON
        gen.writeString(genderStr);
    }
}

然后,在需要轉換的字段上用@JsonSerialize注解指定這個序列化器:

public class User {
    private Long id;
    private String userName;

    // 用自定義序列化器處理gender字段
    @JsonSerialize(using = GenderSerializer.class)
    private Integer gender; // 1→男,2→女

    // getter/setter 省略
}

這樣一來,序列化 User 對象時,gender: 1會自動變成"gender": "男",不用在業務代碼里寫 if-else 了,清爽!

6.2 自定義反序列化器:前端傳 “男”→后端存 1

要是前端傳的是 “男”、“女”,后端需要轉成 1、2 存數據庫,就需要自定義反序列化器,繼承StdDeserializer:

// 性別反序列化器:String(男/女)→ Integer(1/2)
publicclass GenderDeserializer extends StdDeserializer<Integer> {

    public GenderDeserializer() {
        this(null);
    }

    protected GenderDeserializer(Class<?> vc) {
        super(vc);
    }

    // 核心方法:反序列化邏輯
    @Override
    public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        // 拿到前端傳的字符串(比如“男”)
        String genderStr = p.getText();
        // 轉換邏輯:男→1,女→2,其他→0(未知)
        returnswitch (genderStr) {
            case"男" -> 1;
            case"女" -> 2;
            default -> 0;
        };
    }
}

然后在字段上用@JsonDeserialize注解指定:

public class User {
    private Long id;
    private String userName;

    // 序列化用GenderSerializer,反序列化用GenderDeserializer
    @JsonSerialize(using = GenderSerializer.class)
    @JsonDeserialize(using = GenderDeserializer.class)
    private Integer gender;

    // getter/setter 省略
}

這樣前端傳"gender": "男",反序列化后gender就是 1;后端存 1,序列化后返回"gender": "男",完美閉環。

6.3 全局注冊自定義序列化器

要是很多字段都需要用同一個序列化器(比如所有性別字段),每個字段都加@JsonSerialize太麻煩,這時候可以全局注冊序列化器。

在 SpringBoot 里,寫一個Jackson2ObjectMapperBuilderCustomizer的 Bean,把自定義序列化器注冊進去:

@Configuration
publicclass JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            // 全局注冊性別序列化器:所有Integer類型的gender字段都用這個序列化器
            builder.serializerByType(Integer.class, new GenderSerializer());
            // 全局注冊性別反序列化器
            builder.deserializerByType(Integer.class, new GenderDeserializer());

            // 要是只想針對特定字段(比如字段名叫gender),可以用Module
            SimpleModule module = new SimpleModule();
            // 這里的“gender”是字段名,指定這個字段用GenderSerializer
            module.addSerializer("gender", new GenderSerializer());
            module.addDeserializer("gender", new GenderDeserializer());
            builder.modules(module);
        };
    }
}

這里有兩種方式:

  1. serializerByType:按類型注冊,比如所有Integer類型的字段都用這個序列化器(適合所有同類型字段都需要轉換的場景)
  2. addSerializer(字段名, 序列化器):按字段名注冊,只有指定字段名的字段才用這個序列化器(適合特定字段的場景)

根據自己的需求選就行,全局注冊后,就不用在每個字段上寫注解了,更高效。

七、SpringBoot 高級配置:Jackson2ObjectMapperBuilderCustomizer 才是王道

前面我們講了很多配置,比如全局日期格式、字段命名策略、自定義序列化器,有些是在application.yml里配的,有些是用 Bean 配置的。其實 SpringBoot 推薦用Jackson2ObjectMapperBuilderCustomizer來統一管理所有 ObjectMapper 的配置,這樣所有配置都在一個地方,方便維護。

Jackson2ObjectMapperBuilderCustomizer是一個函數式接口,通過它可以自定義Jackson2ObjectMapperBuilder,而Jackson2ObjectMapperBuilder又會用來創建 ObjectMapper 實例,所以用它配置,能覆蓋所有 ObjectMapper 的參數。

咱寫一個完整的配置類,把前面講的痛點解決方案都整合進去:

@Configuration
publicclass JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        // 函數式接口,用lambda表達式實現
        return builder -> {
            // 1. 日期配置
            builder.dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")) // Date類型格式
                  .timeZone(TimeZone.getTimeZone("GMT+8")) // 時區
                  .modules(new JavaTimeModule() // JDK8日期類型支持
                          .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                          .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))));

            // 2. null值處理
            builder.serializationInclusion(JsonInclude.Include.ALWAYS) // 顯示所有null值
                  .featuresToEnable(SerializationFeature.INDENT_OUTPUT) // 格式化JSON(開發環境用,生產環境關閉)
                  .featuresToDisable(SerializationFeature.WRITE_NULL_MAP_VALUES); // 忽略Map中的null值(可選)

            // 3. 字段命名策略
            builder.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); // 駝峰轉下劃線

            // 4. 自定義序列化器/反序列化器
            SimpleModule module = new SimpleModule();
            module.addSerializer(Integer.class, new GenderSerializer()) // 性別序列化
                  .addDeserializer(Integer.class, new GenderDeserializer()) // 性別反序列化
                  .addSerializer(Long.class, new MoneySerializer()) // 金額序列化(分→元)
                  .addDeserializer(Long.class, new MoneyDeserializer()); // 金額反序列化(元→分)
            builder.modules(module);

            // 5. 其他配置:比如允許單引號、允許非標準JSON格式
            builder.featuresToEnable(
                    JsonParser.Feature.ALLOW_SINGLE_QUOTES, // 允許JSON里用單引號(比如'user_name':'張三')
                    JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, // 允許字段名不加引號(比如user_name:'張三')
                    DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT // 空字符串轉null
            );
        };
    }

    // 金額序列化器:Long(分)→ Double(元)
    staticclass MoneySerializer extends StdSerializer<Long> {
        public MoneySerializer() {
            this(null);
        }
        protected MoneySerializer(Class<Long> t) {
            super(t);
        }
        @Override
        public void serialize(Long money, JsonGenerator gen, SerializerProvider provider) throws IOException {
            // 分轉元,保留兩位小數
            gen.writeNumber(BigDecimal.valueOf(money).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).doubleValue());
        }
    }

    // 金額反序列化器:Double(元)→ Long(分)
    staticclass MoneyDeserializer extends StdDeserializer<Long> {
        public MoneyDeserializer() {
            this(null);
        }
        protected MoneyDeserializer(Class<?> vc) {
            super(vc);
        }
        @Override
        public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            // 元轉分,四舍五入
            Double moneyDouble = p.getDoubleValue();
            return BigDecimal.valueOf(moneyDouble).multiply(new BigDecimal(100)).setScale(0, RoundingMode.HALF_UP).longValue();
        }
    }
}

這個配置類整合了:

  • 日期處理(Date 和 LocalDateTime 都搞定)
  • null 值顯示
  • 字段名駝峰轉下劃線
  • 性別和金額的自定義序列化 / 反序列化
  • 允許單引號、空字符串轉 null 等友好配置

這樣一來,所有 ObjectMapper 的配置都在一個地方,后續要修改某個配置,直接改這里就行,不用到處找,非常優雅。

這里提個小建議:開發環境可以開啟SerializationFeature.INDENT_OUTPUT(格式化 JSON),方便調試;生產環境要關閉,因為格式化會增加 JSON 的體積,影響接口性能。

八、性能優化:ObjectMapper 線程安全,別再 “買杯子” 了

之前我們說過 “別瞎 new ObjectMapper”,除了配置不生效的問題,還有性能問題。

ObjectMapper 的創建成本很高,它需要加載很多模塊、初始化序列化器 / 反序列化器,要是每次用都 new 一個,就像每次喝水都新買個杯子,喝完就扔,太浪費資源了。

而且 ObjectMapper 是線程安全的!只要初始化后不修改它的配置(比如不調用setDateFormat、registerModule這些方法),多個線程同時用它序列化 / 反序列化,完全沒問題。

所以在 SpringBoot 里,最佳實踐是:

  1. 用@Autowired注入 SpringBoot 自動配置的 ObjectMapper(或者自己用Jackson2ObjectMapperBuilderCustomizer配置的)
  2. 不要每次用都 new ObjectMapper
  3. 不要在多線程環境下修改 ObjectMapper 的配置

比如你寫個工具類,也應該注入 ObjectMapper,而不是自己 new:

@Component
publicclass JsonUtils {

    privatefinal ObjectMapper objectMapper;

    // 注入,不是new!
    public JsonUtils(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    // 序列化
    public <T> String toJson(T obj) throws JsonProcessingException {
        return objectMapper.writeValueAsString(obj);
    }

    // 反序列化
    public <T> T fromJson(String json, Class<T> clazz) throws JsonProcessingException {
        return objectMapper.readValue(json, clazz);
    }

    // 反序列化復雜類型(List、Map)
    public <T> T fromJson(String json, TypeReference<T> typeReference) throws JsonProcessingException {
        return objectMapper.readValue(json, typeReference);
    }
}

這樣工具類里的 ObjectMapper 是單例的,性能好,而且配置和 SpringBoot 全局一致,不會出現配置不生效的問題。

九、異常處理:JSON 錯了別返回 500,友好點

最后再聊聊異常處理。當 ObjectMapper 序列化 / 反序列化出錯時(比如 JSON 格式錯誤、字段類型不匹配),會拋出JsonProcessingException(序列化)或JsonMappingException(反序列化)。

要是不處理這些異常,SpringBoot 會默認返回 500 錯誤,前端看到 “服務器內部錯誤”,根本不知道哪里錯了。咱得捕獲這些異常,返回友好的提示,比如 “JSON 格式錯誤,請檢查參數”。

9.1 全局異常處理:用 @RestControllerAdvice

寫一個全局異常處理器,捕獲 Jackson 相關的異常:

@RestControllerAdvice
publicclass GlobalExceptionHandler {

    // 捕獲序列化異常(比如對象里有循環引用)
    @ExceptionHandler(JsonProcessingException.class)
    public ResponseEntity<ErrorResult> handleJsonProcessingException(JsonProcessingException e) {
        ErrorResult result = new ErrorResult(
                HttpStatus.BAD_REQUEST.value(),
                "JSON序列化失敗:" + e.getMessage()
        );
        returnnew ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
    }

    // 捕獲反序列化異常(比如JSON格式錯、字段類型不匹配)
    @ExceptionHandler(JsonMappingException.class)
    public ResponseEntity<ErrorResult> handleJsonMappingException(JsonMappingException e) {
        // 提取錯誤字段(比如哪個字段類型不匹配)
        String field = e.getPath().stream()
                .map(JsonMappingException.Reference::getFieldName)
                .findFirst()
                .orElse("未知字段");
        String message = "JSON反序列化失敗:字段[" + field + "]" + e.getOriginalMessage();
        ErrorResult result = new ErrorResult(
                HttpStatus.BAD_REQUEST.value(),
                message
        );
        returnnew ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
    }

    // 錯誤響應體
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    publicstaticclass ErrorResult {
        private Integer code; // 錯誤碼
        private String message; // 錯誤信息
    }
}

這樣一來,當出現 JSON 錯誤時,會返回:

{
  "code": 400,
  "message": "JSON反序列化失敗:字段[age]Cannot deserialize value of type `java.lang.Integer` from String \"二十\": not a valid Integer value"
}

前端能清楚知道是哪個字段錯了,錯在哪里,不用再跟后端反復溝通 “我傳的參數沒問題啊”,效率高多了。

十、總結:優雅使用 ObjectMapper 的 9 個要點

嘮了這么多,最后總結一下,SpringBoot 里優雅用 ObjectMapper 的核心就是這 9 點:

  1. 別瞎 new:用@Autowired注入 SpringBoot 自動配置的 ObjectMapper,別自己 new;
  2. 全局配置優先:日期格式、null 值處理、字段命名策略,先在application.yml或Jackson2ObjectMapperBuilderCustomizer里配全局的,減少重復代碼;
  3. 局部配置補充:個別字段特殊需求,用@JsonFormat、@JsonInclude、@JsonProperty等注解覆蓋全局;
  4. 復雜類型用 TypeReference:反序列化 List、Map 等泛型類型,一定要用new TypeReference<>() {};
  5. 自定義序列化抽離邏輯:特殊轉換(比如性別、金額)用自定義序列化器,別在業務代碼里堆 if-else;
  6. 全局注冊序列化器:多個字段用同一個序列化器,全局注冊比每個字段加注解更高效;
  7. 線程安全要記住:ObjectMapper 是線程安全的,初始化一次全局用,別頻繁 new;
  8. 開發生產環境區分:開發環境開啟 JSON 格式化,生產環境關閉,兼顧調試和性能;
  9. 異常處理要友好:捕獲 Jackson 異常,返回明確的錯誤信息,別讓前端猜。

其實 ObjectMapper 這玩意兒,你用順了之后會發現,它比你想象中靈活多了。以前踩的那些坑,大多是因為沒搞懂 SpringBoot 的自動配置邏輯,或者沒掌握它的高級用法。

責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2023-11-23 08:25:31

String性能

2021-04-20 10:50:38

Spring Boot代碼Java

2023-03-23 22:46:38

Spring限流機制

2023-08-01 08:54:02

接口冪等網絡

2024-10-11 11:46:40

2021-10-15 06:58:57

零信任高危漏洞

2022-02-15 17:56:19

SpringBoot日志

2024-10-16 12:23:55

技巧Spring驗證

2022-08-21 14:00:11

消息中間件MQ

2017-07-27 16:18:18

開源項目使用

2021-11-10 10:03:18

SpringBootJava代碼

2020-10-25 19:58:04

Pythonic代碼語言

2025-07-29 02:15:00

2014-06-09 10:51:59

2024-12-17 08:20:50

2023-03-06 11:36:13

SpingBoot注解

2021-02-05 11:36:42

數據業務指標

2017-07-07 16:57:35

代碼Python

2017-06-26 09:40:50

Python代碼寫法

2025-02-26 08:46:31

點贊
收藏

51CTO技術棧公眾號

超碰地址久久| 欧美人xxx| 在线精品一区| 欧美日高清视频| 亚洲图片都市激情| 国产在线精品观看| 亚洲欧美另类综合| 亚洲影院免费| 精品久久久av| 毛茸茸多毛bbb毛多视频| 成人网ww555视频免费看| 91亚洲永久精品| 午夜精品一区二区三区在线播放| 免费看涩涩视频| 国产嫩草在线视频| 久久久亚洲欧洲日产国码αv| 91精品国产自产91精品| 久久一级免费视频| 欧美一级二级三级视频| 欧美肥胖老妇做爰| 凹凸日日摸日日碰夜夜爽1| 欧美xxx.com| 国产很黄免费观看久久| 欧美国产一区二区三区| 国产一区二区三区视频播放| 日韩欧国产精品一区综合无码| 中文字幕电影一区| 国产乱码精品一区二区三区不卡| 日韩精品一区二区不卡| 99精品视频精品精品视频| 国产视频亚洲精品| 涩视频在线观看| h片在线观看视频免费| 国产91精品免费| 97精品国产97久久久久久春色| 国产吞精囗交久久久| 老司机亚洲精品一区二区| 欧美在线视频你懂得| 日韩人妻精品无码一区二区三区| av在线电影观看| 26uuu国产在线精品一区二区| 国产精品久久久久av| 国产成人无码精品久在线观看 | 亚洲不卡在线观看| 特级黄色录像片| 欧美日韩xx| 中文字幕亚洲电影| 亚洲人体一区| 在线观看免费网站黄| 久久精品一区二区三区四区| 欧美国产一二三区| 欧美一区二区视频| 91麻豆6部合集magnet| 九色91在线视频| 亚洲 欧美 自拍偷拍| 久久99国产精品麻豆| 国内免费久久久久久久久久久| 三区四区在线观看| 欧美日韩一二三四| 色哟哟网站入口亚洲精品| 亚洲不卡的av| 日韩啪啪网站| 亚洲欧洲在线视频| 亚洲最大成人综合网| 88久久精品| 精品婷婷伊人一区三区三| 九九视频精品在线观看| 福利一区和二区| 欧美精品自拍偷拍| 日韩视频在线免费看| 亚洲综合av一区二区三区| 欧美丝袜自拍制服另类| 久久久999免费视频| www久久日com| 亚洲自拍偷拍麻豆| 1024精品视频| 久久亚洲人体| 精品精品国产高清一毛片一天堂| 久热精品在线播放| 国产乱码精品一区二区三区亚洲人| 一本大道久久a久久精品综合| 久久这里只有精品8| 国产伦理精品| 午夜婷婷国产麻豆精品| 成年人三级视频| 99riav视频在线观看| 亚洲图片一区二区| 超碰10000| 三级在线看中文字幕完整版| 亚洲福利视频导航| 日韩无套无码精品| 激情综合婷婷| 精品视频久久久久久| 91资源在线播放| 韩国在线视频一区| 国产精品r级在线| www.五月婷婷| 国产三级精品视频| 青娱乐一区二区| 国产视频二区在线观看| 亚洲精品亚洲人成人网在线播放| 少妇高潮流白浆| 麻豆视频在线观看免费网站黄| 午夜精品在线看| 91制片厂毛片| 日韩精品福利一区二区三区| 日韩av影院在线观看| 波兰性xxxxx极品hd| 一区二区三区成人精品| 国产在线拍偷自揄拍精品| 亚州男人的天堂| 亚洲同性gay激情无套| 亚洲精品无码久久久久久| xx欧美视频| 欧美日韩性生活| 久久久久成人精品无码中文字幕| 亚洲欧洲美洲国产香蕉| 欧美成人黄色小视频| 亚洲成人av网址| 99精品视频在线观看| 欧洲美女和动交zoz0z| 99久久婷婷国产综合精品首页| 欧美视频一区在线观看| 久久一区二区电影| 黄色精品一区| 亚洲一区二区在线| 欧美 日韩 中文字幕| 1区2区3区欧美| 欧美亚洲日本一区二区三区| 久久九九精品视频| 日韩国产精品一区| 久久av高潮av无码av喷吹| 精品一区二区三区免费视频| 日韩电影在线播放| 自由的xxxx在线视频| 欧美日韩和欧美的一区二区| 欧美成人国产精品一区二区| 国产一级一区二区| 狠狠色噜噜狠狠色综合久| 国产毛片在线| 色一区在线观看| 成人免费播放视频| 九九综合在线| 欧美成人三级视频网站| 97超碰人人草| 亚洲欧洲精品成人久久奇米网| 日本丰满少妇xxxx| 成人中文字幕视频| 韩国国内大量揄拍精品视频| www.av黄色| 亚洲一区二区三区不卡国产欧美| 国产精品久久久久9999小说| 国产探花在线精品一区二区| 日本久久精品视频| 国产中文字幕在线观看| 91极品视觉盛宴| 国产精品久久久久久久av| 蜜桃一区二区三区在线| 亚洲无玛一区| 国产色99精品9i| 欧美激情亚洲另类| 五月天激情婷婷| 亚洲激情欧美激情| 久久久高清视频| 亚洲影院在线| 婷婷四房综合激情五月| 久久露脸国产精品| 性久久久久久久久久久| re久久精品视频| 成人福利在线视频| 手机av在线播放| 亚洲精品98久久久久久中文字幕| 老妇女50岁三级| 不卡电影一区二区三区| 人妻熟女一二三区夜夜爱| 国产一区二区精品福利地址| 成人网在线观看| sm久久捆绑调教精品一区| 亚洲欧洲第一视频| 国产精品九九九九| 午夜av电影一区| 色www亚洲国产阿娇yao| 国产精品99久久久久久有的能看| 亚洲精品永久www嫩草| 国产视频网站一区二区三区| 欧美精品国产精品日韩精品| 国产小视频在线| 激情亚洲一区二区三区四区| www.狠狠爱| 麻豆精品蜜桃视频网站| av在线播放天堂| 日本电影一区二区| 国产精品三级在线| 丁香花在线电影| 一本一本久久a久久精品综合小说| 亚洲熟妇无码乱子av电影| 国产精品久久久久久久久久免费看 | 国产美女www爽爽爽视频| 日韩主播视频在线| 蜜桃av噜噜一区二区三| 99久久综合国产精品二区| 欧美极品少妇xxxxⅹ免费视频| 精品人妻一区二区三区含羞草| 日韩理论片一区二区| 好吊一区二区三区视频| 国产一区二区在线免费观看| 国产又粗又长又爽视频| 日韩免费一级| 国产美女精品免费电影| 国产精品实拍| 亚洲欧美一区二区三区四区| www.四虎在线观看| 欧美视频在线一区二区三区| av中文在线播放| 一区二区三区加勒比av| 战狼4完整免费观看在线播放版| 美女视频黄频大全不卡视频在线播放| 亚洲精品成人久久久998| 麻豆mv在线观看| 久久成人精品视频| 天堂网在线中文| 欧美一区二区三区在线| 国产网站在线看| 亚洲精品国产精华液| 中文字幕91视频| 成人永久免费视频| 91热视频在线观看| 蜜臀久久99精品久久久久久9| 国产又粗又大又爽的视频| 欧美系列电影免费观看| 秋霞久久久久久一区二区| 宅男噜噜噜66国产精品免费| 国产精品黄色av| 中文在线最新版地址| 97视频在线免费观看| 国产精品69xx| 最近2019年中文视频免费在线观看| 国产乱码精品一区二区| 五月激情六月综合| 久久精品亚洲无码| 亚洲一区二三区| 免费在线视频一区二区| 一卡二卡三卡日韩欧美| 青娱乐国产精品| 亚洲一区二区三区自拍| 精品一区二区三区人妻| 亚洲一区二区高清| 国产无遮挡裸体免费视频| 国产欧美一区二区精品秋霞影院| 国产探花一区二区三区| 国产电影精品久久禁18| 亚洲熟妇一区二区| 美女精品一区二区| 欧美美女性视频| 久久99精品久久只有精品| 午夜精品久久久久久久99热影院| 国产欧美69| 日韩av片在线看| 欧美私人啪啪vps| 免费看欧美黑人毛片| 91久久国产| 少妇久久久久久被弄到高潮| 日本精品黄色| 日本丰满少妇黄大片在线观看| 欧美禁忌电影网| 色姑娘综合av| 亚洲国产精品成人| 亚洲mv在线看| 影音先锋日韩精品| 成人在线观看你懂的| 欧美亚洲一级| 97超碰成人在线| 成人91在线观看| 中文字幕第24页| 一区二区三区欧美久久| 国产精品美女久久久久av爽| 色综合av在线| 91久久国语露脸精品国产高跟| 在线免费观看不卡av| 97视频免费在线| 亚洲国产精品一区二区三区| 大乳在线免费观看| 美女国内精品自产拍在线播放| 日本美女在线中文版| 一区二区三区动漫| 国产高清一级毛片在线不卡| 久久视频在线看| 国产三区在线观看| 2023亚洲男人天堂| 国产aⅴ精品一区二区四区| 精品国产一区二区三区麻豆免费观看完整版| 日韩精品中文字幕吗一区二区| 成人乱色短篇合集| 2019中文亚洲字幕| 免费成人深夜夜行视频| 天天综合亚洲| 亚洲精品无码久久久久久| 国产米奇在线777精品观看| 无码人妻精品一区二区三区温州| www精品美女久久久tv| 欧美日韩色视频| 色综合一区二区三区| www.日日夜夜| 久久久国产精品x99av| 偷拍自拍在线看| 成人黄动漫网站免费| 久久亚洲国产| 丰满少妇被猛烈进入高清播放| 免费亚洲一区| 尤物av无码色av无码| 国产麻豆一精品一av一免费| 亚洲色成人网站www永久四虎 | 网站永久看片免费| 色婷婷综合久色| 91久久精品国产91性色69| 91精品国产91久久久久久最新毛片 | 污的网站在线观看| 国产女人18毛片水18精品| 天天做夜夜做人人爱精品| 日韩精品久久一区二区| 久久99精品国产麻豆婷婷| 色婷婷av777| 中文字幕日韩av资源站| 久久激情免费视频| 91精品国产高清一区二区三区蜜臀 | 91破解版在线观看| 亚洲尤物视频网| 99视频精品全部免费在线视频| 99在线观看视频免费| 极品少妇xxxx精品少妇偷拍| 精品无码在线观看| 色呦呦国产精品| 日韩专区一区二区| 97成人精品区在线播放| www国产精品| 国产乱淫av片杨贵妃| 国产不卡视频一区| 麻豆亚洲av熟女国产一区二| 欧美日韩在线第一页| 91久久精品国产91性色69| 中文字幕亚洲无线码a| 日本综合久久| 亚洲福利av| 捆绑调教美女网站视频一区| 日本黄色激情视频| 欧美日韩国产三级| 国产一二三区在线观看| 欧美自拍视频在线| 亚洲成人五区| 男人天堂av片| 国产乱一区二区| 日本少妇高潮喷水xxxxxxx| 亚洲美女屁股眼交3| 国产成人无码av| 国产亚洲一区精品| 日韩黄色三级| 亚洲精品国产suv一区88| 菠萝蜜视频在线观看一区| 免费看特级毛片| 日韩精品一区二区三区swag| 九色91在线| 久久艳妇乳肉豪妇荡乳av| 欧美日韩天堂| 亚洲一区二区三区综合| 一本大道久久a久久综合| 日本亚洲精品| 91传媒视频在线观看| 亚洲黄色成人| av永久免费观看| 色婷婷av一区二区三区大白胸| 丰满肥臀噗嗤啊x99av| 久久不射电影网| 丝袜久久网站| 日本在线播放一区二区| 亚洲一区二区三区视频在线播放| 国产孕妇孕交大片孕| 97免费视频在线播放| 精品国产一区二区三区久久久蜜臀| 日本网站免费在线观看| 国产欧美日韩精品在线| 蜜臀精品一区二区三区| 精品亚洲一区二区三区在线观看 | 免费人成在线观看网站| 久久露脸国产精品| 久9re热视频这里只有精品| 天天碰免费视频| 一区二区三区中文字幕| 青青草娱乐在线| 亚洲va欧美va国产综合久久| 999在线观看精品免费不卡网站| 秋霞午夜鲁丝一区二区| 狠狠躁夜夜躁久久躁别揉| 日本免费在线视频| 亚洲精品免费一区二区三区| 国产亚洲精品自拍| 黄色香蕉视频在线观看| 欧美一级xxx| 三级成人在线| 亚洲高清资源综合久久精品| 成人免费高清视频在线观看|