当前位置: 首页 > news >正文

Spring Boot Jackson 序列化常用配置详解

一、引言

在当今的 Web 开发领域,JSON(JavaScript Object Notation)已然成为数据交换的中流砥柱。无论是前后端分离架构下前后端之间的数据交互,还是微服务架构里各个微服务之间的通信,JSON 都承担着至关重要的角色 。它以简洁的文本格式、轻量级的数据结构和良好的可读性、可解析性,在众多数据格式中脱颖而出,被广泛应用于各类场景。

在 Java 开发体系中,处理 JSON 数据的工具丰富多样,而 Jackson 则是其中的佼佼者,更是 Spring Boot 默认集成的 JSON 处理工具。Jackson 提供了强大且丰富的 API,能够便捷地实现 Java 对象与 JSON 数据之间的相互转换,涵盖数据绑定、流式处理以及树模型操作等多项实用功能。借助 Jackson,我们可以轻松地将 HTTP 请求中的 JSON 数据转换为 Java 对象,也能将 Java 对象转换为 JSON 格式返回给前端或其他服务。

不过,Jackson 的默认配置在实际开发中往往难以完全契合我们的复杂需求。比如,在处理日期格式时,默认的时间戳表示可能并不符合业务要求;对于空值的处理,默认忽略的方式也许无法满足特定场景;面对复杂类型和自定义类型,默认的序列化逻辑也可能无法达到预期效果。因此,深入了解并掌握 Jackson 的序列化配置,对于优化应用性能、提升开发效率以及满足多样化的业务需求至关重要。接下来,让我们一同深入探索 Spring Boot 中 Jackson 序列化的常用配置。

二、Jackson 简介

2.1 Jackson 的定义与功能

Jackson 是一款在 Java 领域中被广泛应用的高效 JSON 处理库,它为 Java 开发者提供了一整套便捷、强大的工具,用以轻松实现 Java 对象与 JSON 数据之间的相互转换 。在当今的软件开发场景中,尤其是在 Web 应用开发、微服务架构以及数据存储与传输等领域,JSON 作为一种轻量级的数据交换格式,凭借其简洁易读、易于解析和生成的特点,成为了数据交互的事实标准。而 Jackson 库的出现,使得 Java 开发者能够更加高效地处理 JSON 数据,大大提升了开发效率和应用性能。

Jackson 库的核心功能之一是数据绑定,它能够将 JSON 数据准确无误地映射到 Java 对象,反之亦然。例如,在一个典型的 Web 应用中,前端通过 HTTP 请求向后端发送 JSON 格式的数据,后端使用 Jackson 库的反序列化功能,将接收到的 JSON 数据转换为对应的 Java 对象,方便进行业务逻辑处理;处理完成后,再通过 Jackson 库的序列化功能,将 Java 对象转换为 JSON 格式的数据返回给前端。这种数据绑定的功能不仅方便了前后端之间的数据交互,还确保了数据的一致性和准确性。

除了数据绑定,Jackson 还支持流式处理,这使得它在处理大型 JSON 数据时表现出色。流式处理允许 Jackson 逐块读取和处理 JSON 数据,而无需一次性将整个 JSON 数据加载到内存中,从而大大降低了内存消耗,提高了处理效率。在处理大数据量的 JSON 文件或高并发的 JSON 数据传输时,流式处理的优势尤为明显。

此外,Jackson 还提供了树模型支持,它允许开发者将 JSON 数据解析为类似于 DOM 树的结构,便于对 JSON 数据的各个节点进行灵活操作。通过树模型,开发者可以方便地读取、修改、添加或删除 JSON 数据中的节点,实现对 JSON 数据的精细化处理。比如,在需要对复杂的 JSON 数据进行筛选、合并或转换时,树模型能够提供更加直观和高效的操作方式。

2.2 Jackson 的核心模块

Jackson 库由多个功能强大的模块组成,这些模块相互协作,共同为开发者提供了全面的 JSON 处理能力。其中,最为核心的模块包括 jackson-core、jackson-databind 和 jackson-annotations,它们各自承担着不同的职责,在 JSON 处理过程中发挥着不可或缺的作用。

jackson-core 是 Jackson 库的核心基础模块,它提供了底层的 JSON 处理功能,定义了低级的流式 API,包括了 JSON 处理细节。该模块包含了 JsonParser 和 JsonGenerator 两个关键类,JsonParser 用于解析 JSON 数据,它能够将 JSON 数据逐字符或逐块地读取,并将其转换为一系列的 JSON 令牌(Token),开发者可以通过迭代这些令牌来逐步处理 JSON 数据;JsonGenerator 则用于生成 JSON 数据,它提供了一系列方法,用于将 Java 对象转换为 JSON 格式的字符串或流。例如,在将一个 Java 对象序列化为 JSON 字符串时,JsonGenerator 会按照 JSON 的语法规则,将对象的属性和值逐个写入输出流中,最终生成完整的 JSON 字符串。jackson-core 模块的高效实现,为整个 Jackson 库的高性能提供了坚实的保障。

jackson-databind 是 Jackson 库中用于数据绑定的核心模块,也是开发者在日常开发中使用最为频繁的模块之一。它在 jackson-core 的基础上,实现了 Java 对象与 JSON 数据之间的自动映射和转换,提供了基于 “对象绑定” 解析的相关 API (ObjectMapper)和 “树模型” 解析的相关 API (JsonNode)。通过 ObjectMapper 类,开发者可以方便地将 JSON 数据转换为 Java 对象,或者将 Java 对象转换为 JSON 数据。例如,使用 ObjectMapper 的 readValue 方法,可以将一个 JSON 字符串反序列化为指定类型的 Java 对象;使用 writeValue 方法,则可以将 Java 对象序列化为 JSON 字符串。此外,jackson-databind 还支持复杂对象图的处理,包括嵌套对象、集合、循环引用等,能够满足各种复杂业务场景的需求。

jackson-annotations 是 Jackson 库的注解模块,它提供了一系列丰富的注解,用于配置 JSON 序列化与反序列化的行为。这些注解可以直接应用于 Java 类、属性或方法上,通过简单的注解配置,开发者能够灵活地控制 JSON 数据的转换过程。例如,@JsonIgnore 注解可以用于忽略某个属性,使其在序列化和反序列化过程中不被处理;@JsonProperty 注解可以用于指定属性在 JSON 中的名称,实现属性名的自定义映射;@JsonFormat 注解则可以用于指定日期、时间等类型的格式化方式,确保数据在 JSON 中的格式符合业务需求。jackson-annotations 模块的存在,使得 Jackson 库的使用更加灵活和便捷,能够满足不同项目的个性化需求。

在 Spring Boot 项目中,jackson-databind 模块会被自动引入,这是因为 Spring Boot 默认使用 Jackson 作为 JSON 处理工具,而 jackson-databind 是实现数据绑定的关键模块。通常情况下,开发者无需手动添加该依赖,Spring Boot 会自动完成相关的配置和依赖管理,大大简化了开发流程。不过,在某些特殊情况下,如需要使用特定版本的 Jackson 库或添加额外的模块功能时,开发者可能需要手动调整依赖配置。

三、Spring Boot 中默认的 Jackson 配置

3.1 自动配置

Spring Boot 凭借其强大的自动配置功能,在项目搭建时就默认集成了 Jackson ,这使得开发者无需进行繁琐的手动配置,即可轻松实现 JSON 数据的处理。当我们在项目中引入spring-boot-starter-web依赖时,Jackson 相关的依赖也会被自动引入,Spring Boot 会自动配置 Jackson 的相关组件,包括ObjectMapper等核心对象,使其能够在处理 HTTP 请求和响应时无缝对接 JSON 数据转换工作 。

在一个简单的 Spring Boot RESTful 应用中,当我们使用@RequestBody注解来接收前端传递的 JSON 数据时,Spring Boot 会自动识别并调用 Jackson 的反序列化功能,将 JSON 数据转换为对应的 Java 对象。例如,有一个User类,包含name和age两个属性,前端发送一个包含name和age的 JSON 数据,通过@RequestBody User user即可将 JSON 数据绑定到User对象上,无需手动编写任何 JSON 解析代码。同样,当我们使用@ResponseBody注解返回 Java 对象时,Spring Boot 会自动利用 Jackson 将 Java 对象序列化为 JSON 格式的数据返回给前端 。

3.2 默认行为

Spring Boot 默认的 Jackson 配置会根据 HTTP 请求的Content-Type和Accept头来智能选择适当的消息转换器 。这一机制使得 Spring Boot 能够灵活地处理不同类型的请求和响应,确保数据的正确传输和解析。如果请求的Content-Type为application/json,表明请求数据是 JSON 格式,Spring Boot 会自动选择 Jackson 作为消息转换器,将请求体中的 JSON 数据通过 Jackson 反序列化为 Java 对象;在响应时,如果客户端的Accept头中包含application/json,表示客户端期望接收 JSON 格式的数据,Spring Boot 会使用 Jackson 将返回的 Java 对象序列化为 JSON 格式的数据返回给客户端 。

假设我们有一个UserController,其中包含一个处理用户创建的方法:

@RestControllerpublic class UserController {@PostMapping("/user")public User createUser(@RequestBody User user) {return user;}}

在上述代码中,当客户端发送一个Content-Type为application/json的 POST 请求,请求体为{"name":"张三","age":20}时,Spring Boot 会自动使用 Jackson 将这个 JSON 数据反序列化为User对象,并将该对象传递给createUser方法。方法处理完成后,返回的User对象又会被 Jackson 序列化为 JSON 格式的数据返回给客户端,整个过程无需开发者手动干预,极大地提高了开发效率。

3.3 常见问题与解决方案

在使用 Spring Boot 默认的 Jackson 配置过程中,开发者可能会遇到一些常见问题,其中乱码问题和日期格式问题较为突出。

当 JSON 数据中包含特殊字符,如中文时,可能会出现乱码问题。这通常是由于字符编码不一致导致的。解决这个问题的方法是在配置文件(application.properties或application.yml)中设置正确的字符编码。在application.properties中添加如下配置:

spring.http.encoding.charset=UTF-8

spring.http.encoding.enabled=true

spring.http.encoding.force=true

上述配置将 Spring 的 HTTP 编码设置为 UTF-8,并强制应用该编码,确保在数据传输过程中不会出现乱码问题。

默认情况下,Jackson 在处理日期类型时,可能无法按照我们期望的格式进行序列化和反序列化。比如,默认的日期格式可能是时间戳或者不符合业务需求的格式。为了解决这个问题,我们可以通过在配置文件中进行全局配置来指定日期格式。在application.properties中添加:

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

spring.jackson.time-zone=GMT+8

这里将日期格式指定为yyyy-MM-dd HH:mm:ss,并设置时区为东八区(GMT+8),确保日期在 JSON 中的表示符合我们的业务要求 。通过这种方式,无论是在将 Java 的Date对象序列化为 JSON 字符串,还是将 JSON 中的日期字符串反序列化为Date对象时,都会按照指定的格式进行处理。

四、Jackson 序列化常用配置

4.1 全局配置(通过 application.yml)

在 Spring Boot 项目中,通过application.yml文件对 Jackson 进行全局配置是一种便捷且常用的方式,它能够统一设置 Jackson 在整个应用中的行为,确保数据的序列化和反序列化符合项目的整体需求 。

在application.yml文件中,我们可以对日期格式、时区、属性命名策略、空值处理等进行配置。比如,设置日期格式为yyyy-MM-dd HH:mm:ss,时区为东八区(GMT+8),可以这样配置:

spring:

jackson:

date-format: yyyy-MM-dd HH:mm:ss

time-zone: GMT+8

上述配置中,date-format指定了日期在 JSON 中的序列化和反序列化格式,time-zone设置了应用的时区,确保日期在不同环境下的一致性表示。

对于属性命名策略,若希望将 Java 对象中的驼峰式命名属性转换为 JSON 中的下划线命名,可以配置:

spring:

jackson:

property-naming-strategy: SNAKE_CASE

在这种配置下,Java 类中的userName属性,在 JSON 中会被序列化为user_name。

如果希望在序列化时忽略值为null的属性,可以配置:

spring:

jackson:

default-property-inclusion: non_null

这样,当 Java 对象中的某个属性值为null时,在生成的 JSON 数据中该属性将不会出现 。通过这种全局配置方式,能够快速有效地对 Jackson 的序列化行为进行统一控制,提高开发效率和代码的可维护性 。

4.2 注解配置

Jackson 提供了一系列丰富的注解,这些注解能够在类、属性或方法级别上灵活地控制序列化和反序列化的行为,为开发者提供了高度的定制化能力 。

@JsonInclude注解用于指定在序列化时哪些值应该被包含。例如,使用@JsonInclude(JsonInclude.Include.NON_NULL)可以确保只有非空值的属性才会被序列化到 JSON 中。假设我们有一个User类:

@JsonInclude(JsonInclude.Include.NON_NULL)public class User {private String name;private String email;// getters and setters}

当User对象的email属性为null时,在序列化为 JSON 时,email属性将不会出现在 JSON 字符串中 。

@JsonFormat注解主要用于自定义日期、时间等类型的格式化方式。在一个包含LocalDateTime类型属性的Order类中:

public class Order {@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private LocalDateTime createTime;// getters and setters}

上述配置中,pattern指定了日期时间的格式,timezone设置了时区,这样在序列化和反序列化createTime属性时,都会按照指定的格式和时区进行处理 。

@JsonProperty注解用于指定 Java 类的属性名与 JSON 中的字段名之间的映射关系。例如:

public class User {@JsonProperty("user_name")private String name;// getters and setters}

在这个例子中,Java 对象的name属性在序列化为 JSON 时,会被映射为user_name字段。

@JsonIgnore注解则用于指定某个 Java 类的属性在序列化为 JSON 时被忽略。比如:

public class User {private String name;@JsonIgnoreprivate String password;// getters and setters}

在这个User类中,password属性上添加了@JsonIgnore注解,这意味着在将User对象序列化为 JSON 时,password属性将不会被包含在 JSON 数据中,有效保护了敏感信息 。

4.3 自定义序列化器和反序列化器

在某些复杂的业务场景中,Jackson 的默认序列化和反序列化规则可能无法满足需求,此时我们可以通过编写自定义的序列化器(Serializer)和反序列化器(Deserializer)来实现对特定类或属性的精确控制 。

自定义序列化器和反序列化器的实现方式是通过实现JsonSerializer和JsonDeserializer接口。以日期处理为例,假设我们希望将Date类型的日期格式化为yyyy-MM-dd的形式,我们可以编写如下自定义序列化器和反序列化器:

import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.databind.DeserializationContext;import com.fasterxml.jackson.databind.JsonDeserializer;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.SerializerProvider;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;public class CustomDateSerializationExample {public static class DateSerializer extends JsonSerializer < Date > {private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");@Overridepublic void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {String formattedDate = DATE_FORMAT.format(value);gen.writeString(formattedDate);}}public static class DateDeserializer extends JsonDeserializer < Date > {private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");@Overridepublic Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {String dateString = p.getText();try {return DATE_FORMAT.parse(dateString);} catch (Exception e) {throw new IOException("Failed to parse date: " + dateString, e);}}}public static class Person {private String name;private Date birthDate;// 省略构造和getter/setter方法}public static void main(String[] args) throws Exception {SimpleModule module = new SimpleModule();module.addSerializer(Date.class, new DateSerializer());module.addDeserializer(Date.class, new DateDeserializer());ObjectMapper objectMapper = new ObjectMapper();objectMapper.registerModule(module);// 序列化Person person = new Person("John Doe", new SimpleDateFormat("yyyy-MM-dd").parse("2000-01-01"));String json = objectMapper.writeValueAsString(person);System.out.println(json); // 输出:{"name":"John Doe","birthDate":"2000-01-01"}// 反序列化String jsonInput = "{\"name\":\"Jane Smith\",\"birthDate\":\"1990-12-31\"}";Person deserializedPerson = objectMapper.readValue(jsonInput, Person.class);System.out.println(deserializedPerson.getBirthDate()); // 输出:Mon Dec 31 00:00:00 GMT 1990}}

在上述代码中,DateSerializer实现了JsonSerializer<Date>接口,重写了serialize方法,在序列化Date对象时,将其格式化为yyyy-MM-dd的字符串;DateDeserializer实现了JsonDeserializer<Date>接口,重写了deserialize方法,在反序列化时,将符合yyyy-MM-dd格式的字符串解析为Date对象 。通过创建SimpleModule并将自定义的序列化器和反序列化器注册到ObjectMapper中,我们就可以在整个应用中使用自定义的日期处理逻辑 。

4.4 使用 Mix-in Annotations

Mix-in Annotations 是 Jackson 提供的一种强大机制,它允许我们在不修改原始类的情况下,为其添加自定义的序列化和反序列化逻辑,这在处理一些无法直接修改源代码的类或者需要灵活定制序列化规则的场景中非常有用 。

Mix-in Annotations 的原理是通过创建一个独立的 Mix-in 类,并在该类中为原始类添加自定义的注解,然后将 Mix-in 类与原始类关联起来。用 Mix-in Annotations 来控制日期格式的序列化和反序列化:

import com.fasterxml.jackson.annotation.JsonFormat;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.databind.DeserializationContext;import com.fasterxml.jackson.databind.JsonDeserializer;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.annotation.JsonDeserialize;import com.fasterxml.jackson.databind.annotation.JsonSerialize;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;public class CustomDateSerializationExample {public static class Person {private String name;private Date birthDate;// 省略构造函数和getter/setter方法}public static class PersonMixin {@JsonSerialize(using = DateSerializer.class)@JsonDeserialize(using = DateDeserializer.class)@JsonFormat(pattern = "yyyy-MM-dd")private Date birthDate;}public static class DateSerializer extends JsonSerializer < Date > {private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");@Overridepublic void serialize(Date value, JsonGenerator gen, com.fasterxml.jackson.databind.SerializerProvider serializers) throws IOException {String formattedDate = DATE_FORMAT.format(value);gen.writeString(formattedDate);}}public static class DateDeserializer extends JsonDeserializer < Date > {private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");@Overridepublic Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {String dateString = p.getText();try {return DATE_FORMAT.parse(dateString);} catch (Exception e) {throw new IOException("Failed to parse date: " + dateString, e);}}}public static void main(String[] args) throws Exception {ObjectMapper objectMapper = new ObjectMapper();objectMapper.addMixIn(Person.class, PersonMixin.class);// 序列化Person person = new Person();person.setName("John Doe");person.setBirthDate(new SimpleDateFormat("yyyy-MM-dd").parse("2000-01-01"));String json = objectMapper.writeValueAsString(person);System.out.println(json); // 输出:{"name":"John Doe","birthDate":"2000-01-01"}// 反序列化String jsonInput = "{\"name\":\"Jane Smith\",\"birthDate\":\"1990-12-31\"}";Person deserializedPerson = objectMapper.readValue(jsonInput, Person.class);System.out.println(deserializedPerson.getBirthDate()); // 输出:Mon Dec 31 00:00:00 GMT 1990}}

在上述代码中,PersonMixin类为Person类的birthDate属性添加了自定义的序列化和反序列化注解,通过objectMapper.addMixIn(Person.class, PersonMixin.class)将PersonMixin与Person类关联起来,从而实现了对Person类birthDate属性的自定义序列化和反序列化控制,而无需修改Person类的源代码 。

4.5 使用 ObjectMapper 进行配置

在 Java 代码中,我们可以通过ObjectMapper对象对 Jackson 进行配置,这种方式提供了更加灵活和动态的配置能力,尤其适用于需要在运行时根据不同的业务场景进行差异化配置的情况 。

ObjectMapper是 Jackson 库中用于数据绑定的核心类,它提供了丰富的方法来配置序列化和反序列化的行为。我们可以通过ObjectMapper设置日期格式、忽略未知属性、注册自定义模块等。设置日期格式为yyyy-MM-dd,并忽略 JSON 中存在但 Java 对象中不存在的属性,可以这样实现:

import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.SerializationFeature;import com.fasterxml.jackson.databind.module.SimpleModule;import java.text.SimpleDateFormat;public class ObjectMapperConfigurationExample {public static void main(String[] args) throws Exception {ObjectMapper objectMapper = new ObjectMapper();// 设置日期格式SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");objectMapper.setDateFormat(dateFormat);// 忽略未知属性objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 注册自定义模块SimpleModule module = new SimpleModule();// 假设这里有自定义的序列化器和反序列化器,进行注册// module.addSerializer(...);// module.addDeserializer(...);objectMapper.registerModule(module);// 进行序列化和反序列化操作// 例如:// String json = objectMapper.writeValueAsString(someObject);// SomeType obj = objectMapper.readValue(json, SomeType.class);}}

在上述代码中,通过objectMapper.setDateFormat(dateFormat)设置了日期格式;通过objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)配置反序列化时忽略未知属性;通过objectMapper.registerModule(module)注册自定义模块,为ObjectMapper添加了更多的定制化功能 。通过这种方式,我们可以在 Java 代码中根据具体需求对 Jackson 进行精细化配置,满足复杂业务场景的要求 。

五、高级配置与应用场景

5.1 处理复杂数据结构

在实际的业务开发中,我们经常会遇到复杂的数据结构,如嵌套对象和集合。Jackson 凭借其强大的反射机制和灵活的配置能力,能够轻松应对这些复杂数据结构的序列化和反序列化工作 。

假设我们有一个Department类,它包含一个List<User>集合,用于表示部门中的员工列表。而User类又包含其他属性,如name、age和address等。这种嵌套的数据结构在实际业务中非常常见,比如在企业管理系统中,一个部门下有多个员工,每个员工有自己的详细信息 。

public class Department {private String name;private List < User > users;// getters and setters}public class User {private String name;private int age;private String address;// getters and setters}

当我们需要将Department对象序列化为 JSON 时,Jackson 会自动递归地处理嵌套的User对象。在调用ObjectMapper的writeValueAsString方法时,Jackson 会遍历Department对象的所有属性,对于users属性,它会进一步遍历列表中的每个User对象,并将它们转换为相应的 JSON 键值对 。

ObjectMapper objectMapper = new ObjectMapper();Department department = new Department();department.setName("研发部");List<User> users = new ArrayList<>();User user1 = new User();user1.setName("张三");user1.setAge(25);user1.setAddress("北京市海淀区");users.add(user1);User user2 = new User();user2.setName("李四");user2.setAge(30);user2.setAddress("上海市浦东新区");users.add(user2);department.setUsers(users);String json = objectMapper.writeValueAsString(department);System.out.println(json);

上述代码执行后,输出的 JSON 字符串如下:

{

"name": "研发部",

"users": [

{

"name": "张三",

"age": 25,

"address": "北京市海淀区"

},

{

"name": "李四",

"age": 30,

"address": "上海市浦东新区"

}

]

}

从输出结果可以看出,Jackson 能够准确地将嵌套的对象和集合转换为 JSON 格式,并且保持数据结构的完整性 。同样,在反序列化时,Jackson 也能根据 JSON 数据正确地构建出嵌套的 Java 对象结构 。

5.2 解决循环引用问题

在对象关系中,循环引用是一个常见的问题,尤其是在存在双向关联的情况下。比如,在一个社交网络应用中,用户之间可能存在相互关注的关系,一个User对象可能会包含一个List<User>集合来表示其关注的用户,而被关注的用户又可能反过来关注这个用户,这就形成了循环引用 。如果在序列化时不加以处理,Jackson 会陷入无限递归,导致栈溢出错误 。

为了解决循环引用问题,Jackson 提供了几种有效的方式,其中常用的是@JsonIdentityInfo、@JsonManagedReference和@JsonBackReference注解 。

@JsonIdentityInfo注解通过为对象生成唯一标识符,在序列化和反序列化过程中,Jackson 会识别并处理这些标识符,从而避免重复处理同一对象,防止循环引用。假设有两个类Author和Book,Author类包含一个List<Book>集合表示其创作的书籍,而Book类又包含一个Author属性表示书籍的作者,形成了双向关联 。

import com.fasterxml.jackson.annotation.JsonIdentityInfo;import com.fasterxml.jackson.annotation.ObjectIdGenerators;import java.util.List;@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")public class Author {private Long id;private String name;private List < Book > books;// getters and setters}public class Book {private Long id;private String title;private Author author;// getters and setters}

在上述代码中,Author类上添加了@JsonIdentityInfo注解,指定使用PropertyGenerator生成器,并以id属性作为唯一标识符。这样,在序列化Author对象及其关联的Book对象时,Jackson 会为每个对象分配一个唯一的标识符,当遇到重复引用时,会使用标识符代替对象本身,从而避免循环引用 。

@JsonManagedReference和@JsonBackReference注解则是通过标记主引用和反向引用的方式来解决循环引用问题。在双向关联中,我们可以在其中一个类的属性上使用@JsonManagedReference注解标记为主引用,在另一个类的对应属性上使用@JsonBackReference注解标记为反向引用 。在序列化时,主引用会被正常序列化,而反向引用会被忽略,从而打破循环引用 。以User和Friend类为例,User类包含一个List<Friend>集合表示其好友列表,Friend类包含一个User属性表示好友关系的反向引用 。

import com.fasterxml.jackson.annotation.JsonBackReference;import com.fasterxml.jackson.annotation.JsonManagedReference;import java.util.List;public class User {private Long id;private String name;@JsonManagedReferenceprivate List < Friend > friends;// getters and setters}public class Friend {private Long id;private String name;@JsonBackReferenceprivate User user;// getters and setters}

在这个例子中,User类的friends属性上使用了@JsonManagedReference注解,Friend类的user属性上使用了@JsonBackReference注解。当序列化User对象及其好友列表时,friends属性会被正常序列化,而Friend对象中的user属性会被忽略,从而避免了循环引用 。

5.3 多态类型的序列化与反序列化

在面向对象编程中,多态是一个重要的特性,它允许我们使用父类的引用指向子类的对象。在 JSON 序列化和反序列化过程中,处理多态类型是一个常见的需求,尤其是在处理继承关系时 。比如,在一个图形绘制系统中,可能有一个抽象的Shape类,以及它的具体子类Circle和Rectangle,在进行数据传输或存储时,我们需要能够正确地序列化和反序列化这些不同类型的Shape对象 。

Jackson 提供了@JsonTypeInfo和@JsonSubTypes注解来处理多态类型。@JsonTypeInfo注解用于指定在序列化和反序列化时如何包含类型信息,@JsonSubTypes注解则用于指定父类的子类型 。

假设有一个抽象类Shape,以及它的两个子类Circle和Rectangle:

import com.fasterxml.jackson.annotation.JsonSubTypes;import com.fasterxml.jackson.annotation.JsonTypeInfo;@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")@JsonSubTypes({@JsonSubTypes.Type(value = Circle.class, name = "circle"),@JsonSubTypes.Type(value = Rectangle.class, name = "rectangle")})public abstract class Shape {// 公共属性和方法}public class Circle extends Shape {private double radius;// getters and setters}public class Rectangle extends Shape {private double width;private double height;// getters and setters}

在上述代码中,Shape类上添加了@JsonTypeInfo注解,指定使用Id.NAME方式来标识类型,将类型信息作为一个名为type的属性包含在 JSON 中;同时添加了@JsonSubTypes注解,指定了Shape类的两个子类型Circle和Rectangle,并分别为它们指定了类型名称circle和rectangle 。

当我们序列化一个Shape对象时,Jackson 会根据对象的实际类型,在 JSON 中添加type属性来标识其具体类型 。

ObjectMapper objectMapper = new ObjectMapper();

Circle circle = new Circle();

circle.setRadius(5.0);

String json = objectMapper.writeValueAsString(circle);

System.out.println(json);

上述代码执行后,输出的 JSON 字符串如下:

{

"type": "circle",

"radius": 5.0

}

在反序列化时,Jackson 会根据type属性的值,选择正确的子类来创建对象 。

String jsonInput = "{\"type\":\"rectangle\",\"width\":10.0,\"height\":5.0}";

Shape shape = objectMapper.readValue(jsonInput, Shape.class);

if (shape instanceof Rectangle) {

Rectangle rectangle = (Rectangle) shape;

System.out.println("Rectangle width: " + rectangle.getWidth());

System.out.println("Rectangle height: " + rectangle.getHeight());

}

通过这种方式,Jackson 能够准确地处理多态类型的序列化和反序列化,确保在不同类型之间进行数据转换时的准确性和一致性 。

六、性能优化与注意事项

6.1 性能优化建议

在使用 Jackson 进行序列化和反序列化时,采取一些性能优化措施能够显著提升应用的整体性能和响应速度,特别是在处理大量数据或高并发请求的场景中 。

尽可能减少不必要的对象创建是提升性能的关键。在自定义序列化器和反序列化器中,应避免在每次调用时创建新的对象,而是使用静态成员变量或单例模式来复用对象。在日期格式化的自定义序列化器中,创建一个静态的SimpleDateFormat对象,而不是每次序列化时都新建一个实例,这样可以减少内存开销和对象创建的时间消耗 。

缓存ObjectMapper对象也是一种有效的优化手段。ObjectMapper的创建和配置通常是比较耗时的操作,尤其是在包含复杂的自定义配置时。通过将ObjectMapper对象缓存起来,避免在每次需要进行序列化或反序列化时重新创建,可以大大提高处理效率 。在一个需要频繁进行 JSON 数据处理的服务类中,可以将ObjectMapper定义为静态成员变量,并在类加载时进行初始化,这样在整个服务类的生命周期内都可以复用该对象 。

根据数据量和应用场景选择合适的序列化方式也至关重要。对于小型对象和简单数据结构,普通的对象绑定方式通常就足够高效;而对于大型数据集合或复杂对象图,流式处理可能是更好的选择。流式处理能够逐块读取和处理数据,避免一次性将大量数据加载到内存中,从而降低内存占用,提高处理大型 JSON 数据的效率 。在处理一个包含大量用户信息的 JSON 文件时,使用流式处理可以在读取文件的同时进行数据处理,而不需要等待整个文件加载完成,大大提升了处理速度和内存利用率 。

6.2 注意事项

在使用 Jackson 进行序列化和反序列化时,需要注意一些关键问题,以确保数据的正确处理和应用的稳定运行 。

注解的使用需要谨慎,不同的注解可能会相互影响,导致意想不到的结果。@JsonFormat和@JsonSerialize注解在处理日期类型时,如果同时使用且配置不一致,可能会造成序列化和反序列化的结果不符合预期。在使用这些注解时,要确保它们的配置相互兼容,并且符合业务需求 。

配置优先级也是一个需要关注的点。全局配置、注解配置以及通过ObjectMapper进行的配置之间存在一定的优先级关系。一般来说,注解配置的优先级高于全局配置,而通过ObjectMapper进行的配置在运行时具有最高的优先级。在进行配置时,要清楚了解这些优先级关系,避免因配置冲突而导致的错误 。如果在application.yml中进行了全局的日期格式配置,同时又在某个类的属性上使用@JsonFormat注解进行了不同的日期格式配置,那么在序列化该属性时,@JsonFormat注解的配置将生效 。

当处理大对象或复杂对象图时,性能和内存消耗可能会成为瓶颈。在这种情况下,要注意避免不必要的嵌套和循环引用,合理设计对象结构,以减少序列化和反序列化的时间和内存开销。可以考虑使用视图(View)来控制序列化的字段,只返回必要的数据,减少数据传输量和处理复杂度 。在一个包含大量属性和关联对象的用户信息类中,如果某些属性在特定的接口中不需要返回,可以使用@JsonView注解来过滤这些属性,提高序列化和反序列化的效率 。

七、总结

在 Spring Boot 开发中,Jackson 作为默认的 JSON 处理工具,扮演着举足轻重的角色。通过本文的详细介绍,我们深入了解了 Jackson 的核心概念、在 Spring Boot 中的默认配置,以及丰富多样的序列化常用配置方式,包括全局配置、注解配置、自定义序列化器和反序列化器、Mix-in Annotations 的使用以及通过 ObjectMapper 进行配置等 。这些配置方式为我们在不同的业务场景下灵活处理 JSON 数据提供了强大的支持 。

同时,我们还探讨了 Jackson 在处理复杂数据结构、解决循环引用问题以及多态类型的序列化与反序列化等高级应用场景中的应用,以及在性能优化和使用过程中的注意事项 。通过合理地运用 Jackson 的这些特性和配置,我们能够提升应用的数据处理能力和性能表现,确保数据在不同系统之间的准确传输和高效交互 。

希望读者在实际项目中能够充分运用这些知识,根据具体的业务需求,灵活配置 Jackson,发挥其最大的优势。也期待大家在实践中不断探索和总结,发现更多关于 Jackson 的高效使用技巧,为项目的开发和优化提供有力的支持 。如果在使用过程中遇到任何问题或有新的见解,欢迎在评论区留言交流,共同进步 。

http://www.dtcms.com/a/283792.html

相关文章:

  • redis速记
  • Jenkins Git Parameter 分支不显示前缀origin/或repo/
  • 【37】MFC入门到精通——MFC中 CString 数字字符串 转 WORD ( CString, WORD/int 互转)
  • 我爱学算法之—— 前缀和(下)
  • 破局 Meme 币永续:跨界融合 Ormer + AI + 舆情监控 的颠覆性框架
  • 日志采集——ZeroMQ的配置
  • MyBatis 之配置与映射核心要点解析
  • 林曦词典|文质彬彬
  • 如何查询pg账号权限 能否创建模式 删表建表
  • Vim多列打开不同文件操作指南
  • 什么是AI-AIGC-AGI-Agent?基本概念与区别的详细解析
  • 【SAP SD】跨公司销售、第三方销售、STO采购(公司间合同配件)
  • 【困难】题解力扣23:合并K个升序链表
  • 删除百度同步空间桌面图标
  • 面试高频题 力扣 200.岛屿数量 洪水灌溉 深度优先遍历 暴力搜索 C++解题思路 每日一题
  • 用Amazon Q Developer命令行工具(CLI)快捷开发酒店入住应用程序
  • 图片画廊浏览(侧重 CSS 网格布局和模态框交互)
  • onUnload页面卸载和onPageScroll监听页面滚动
  • EPLAN 电气制图(十): 绘制继电器控制回路从符号到属性设置(上)
  • C++编程学习(第九天)
  • FastAdmin系统框架通用操作平滑迁移到新服务器的详细步骤-优雅草卓伊凡
  • btstack移植之安全配对(二)
  • 【Linux-云原生-笔记】LVS(Linux virual server)相关
  • C strtok函数应用
  • c++ 模板元编程
  • 深入解析Hadoop HDFS高可用性:原理、故障切换与元数据同步
  • 【AI论文】T-LoRA:无过拟合的单图像扩散模型定制化方案
  • MailAgentProcess.getInstance
  • 进程终止机制详解:退出场景、退出码与退出方式全解析
  • Django中get()与filter()对比