告别数据混乱!精通Spring Boot序列化与反序列化

大家好,我是加洛斯 👨‍💻。作为一名程序员,我深深信奉费曼学习法------教,是最好的学 📚。这里是我的知识笔记与分享,旨在把复杂的东西讲明白。如果发现有误,万分欢迎你帮我指出来 🔍!废话不多说,正文开始 👇

在开发中我们经常能听到的两个词,序列化与反序列化。很多萌新小白听到这个概念肯定是满脑门问号,接下来我将用通俗易懂的语言和妙趣横生的比喻,深入浅出的将序列化与反序列的基础以及原理给大家讲述出来。

一、基础概念

简单来说,序列化反序列化 就是 将数据在不同格式之间进行转换 的过程,目的是为了在不同的系统或不同的上下文之间传输和存储数据

  • 序列化 (Serialization):对象 转换为 可传输或可存储的格式(通常是 JSON、XML 等)。

    • 在 Spring Boot 中的场景: 当你的 Controller 方法返回一个 Java 对象时,Spring Boot 会自动将它序列化成 JSON 字符串,然后通过 HTTP 响应发送给前端(如浏览器、手机 App)。
  • 反序列化 (Deserialization):可传输或可存储的格式 (如 JSON、XML)转换回 对象

    • 在 Spring Boot 中的场景: 当前端通过 HTTP 请求(如 POST、PUT)发送一个 JSON 数据到你的服务器时,Spring Boot 会自动将这个 JSON 字符串反序列化成你 Controller 方法参数中定义的 Java 对象。

那么简单来讲就是,序列化是将对象转化为JSON等格式,反序列化就是将JSON等格式转化为对象。

二、具体应用

Spring Boot 通过Spring MVC 模块和默认集成的Jackson库,让这个过程变得极为简单,但是其背后的原理却是相当复杂,我们先从简单的开始来,看看怎么用以及干了什么。

2.1 反序列

假设前端要创建一个用户,它发送了一个 POST 请求,请求体是 JSON:

json 复制代码
{
  "name": "张三",
  "age": 25,
  "email": "zhangsan@example.com"
}

对应的 User 类(Java Bean):

java 复制代码
// 必须有无参构造函数
// 必须有为所有属性生成的 Getter 和 Setter 方法
// lombok注解 自动生成getter与setter方法与无参构造函数
@Data 
public class User {
    private String name;
    private Integer age;
    private String email;
}

在你的 Spring Boot Controller 中,你会这样写:

java 复制代码
@RestController
public class UserController {

    @PostMapping("/users")
    public ResponseEntity<String> createUser(@RequestBody User user) { 
        // 在这个方法内部,User 对象已经是一个可以直接使用的 Java 对象了
        System.out.println("用户名:" + user.getName());
        System.out.println("年龄:" + user.getage());
        // ... 保存用户到数据库等操作
        return ResponseEntity.ok("用户创建成功");
    }
}

这里发生了什么?

  1. 前端发送 JSON 数据。
  2. Spring Boot 看到 @RequestBody 注解和 User 这个参数类型。
  3. Spring Boot 使用 Jackson反序列化 JSON 字符串,根据 JSON 的 key 匹配 User 类的属性(如 name 匹配 name),并创建一个 User 对象。
  4. 这个创建好的 User 对象被传入你的 createUser 方法中。

这样我们就从前端发送的JSON中解析了我们需要的数据,是不是很简单,现在没有配置任何文件,紧靠SpringBoot自带的Jackson库就完成了反序列化。

2.2 序列化

同样一个 Controller,我们写一个查询用户的方法:

java 复制代码
@RestController
public class UserController {

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // 假设从数据库或其他地方查询到了一个用户
        User user = new User();
        user.setName("李四");
        user.setAge(30);
        user.setEmail("lisi@example.com");
        
        // 直接返回这个对象
        return user;
    }
}

这里发生了什么?

  1. 你的方法返回了一个 User 对象。
  2. Spring Boot 看到返回值后,使用 Jackson序列化 这个 User 对象。
  3. 最终,前端收到的 HTTP 响应体就是这样的 JSON 字符串:
json 复制代码
{
  "name": "李四",
  "age": 30,
  "email": "lisi@example.com"
}

三、Jackson

SpringBoot通过自动配置和默认的 Jackson 集成,极大地简化了 Web 开发中数据格式转换的复杂性。开发者几乎不需要编写转换代码,只需关注业务逻辑(定义对象、编写 Controller),就能轻松实现 RESTful API 的数据交互。我们来学习几个常用的 Jackson 注解。

  • @JsonIgnore:在序列化时忽略某个属性。
java 复制代码
public class User {
    private String name;
    @JsonIgnore // 返回给前端的 JSON 中不会包含 password 字段,保障安全
    private String password;
}
  • @JsonProperty:为属性指定一个别名。
java 复制代码
public class User {
    @JsonProperty("full_name") // 序列化后 JSON 中的 key 是 "full_name"
    private String name;
}
  • @JsonFormat:格式化日期字段。
java 复制代码
public class User {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
}
  • @JsonInclude:控制序列化时包含哪些属性。
java 复制代码
@JsonInclude(JsonInclude.Include.NON_NULL) // 只包含非 null 字段
public class User {
    private String name;
    private Integer age; // 如果 age 为 null,则序列化结果中无此字段
}
  • @JsonView:定义视图,实现不同接口返回不同字段。
java 复制代码
public class Views {
    public static class Public {} // 公共视图
    public static class Internal extends Public {} // 内部视图,继承公共视图
}

public class User {
    @JsonView(Views.Public.class)
    private String name;

    @JsonView(Views.Internal.class)
    private String email;
}

@RestController
public class UserController {
    @GetMapping("/user/public")
    @JsonView(Views.Public.class) // 只序列化 Public 视图的字段
    public User getPublicUser() { ... }

    @GetMapping("/user/internal")
    @JsonView(Views.Internal.class) // 序列化 Internal 和 Public 视图的字段
    public User getInternalUser() { ... }
}
过程 方向 动作 比喻 Spring Boot 中的关键注解
序列化 对象 -> JSON 将Java对象转换为传输/存储格式 打包 (默认行为,无需注解)
反序列化 JSON -> 对象 将传输/存储格式转换为Java对象 拆包组装 @RequestBody

四、原理

Spring Boot 的序列化与反序列化其背后是一套名为 HttpMessageConverter 的可插拔策略接口。

  • 工作原理: 当 Spring MVC 处理一个 HTTP 请求时,它会根据请求的 Content-Type(反序列化)和 Accept(序列化)头,以及目标方法的参数/返回类型,来选择一个合适的 HttpMessageConverter 实现。
  • 可插拔性: 你可以自定义或替换这些转换器,从而支持不同的数据格式(如 JSON、XML、Protobuf 等)。

Spring Boot 通过自动配置,默认为我们注册了以下常用的转换器:

转换器类 支持格式 说明
MappingJackson2HttpMessageConverter JSON 默认,使用 Jackson 库
GsonHttpMessageConverter JSON 使用 Google 的 Gson 库
Jaxb2RootElementHttpMessageConverter XML 处理 XML 格式

如何切换? 例如,如果你想用 Gson 替代 Jackson,只需在 pom.xml 中排除 Jackson 并引入 Gson 依赖,Spring Boot 会自动切换默认的 JSON 转换器。

五、自定义序列化与反序列化器

当默认行为无法满足复杂需求时,可以编写自定义的 JsonSerializerJsonDeserializer。 我们希望将 Money 对象序列化为 "100.00元" 的格式。

java 复制代码
// 1. 自定义序列化器
public class MoneySerializer extends JsonSerializer<Money> {
    @Override
    public void serialize(Money value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 将 Money 对象转换为字符串
        String moneyStr = value.getAmount() + "元";
        gen.writeString(moneyStr);
    }
}

// 2. 自定义反序列化器
public class MoneyDeserializer extends JsonDeserializer<Money> {
    @Override
    public Money deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String text = p.getText(); // 例如 "100.00元"
        // 解析字符串,创建 Money 对象
        String amountStr = text.replace("元", "");
        BigDecimal amount = new BigDecimal(amountStr);
        return new Money(amount);
    }
}

// 3. 在 Money 类上使用注解绑定
public class Money {
    private BigDecimal amount;

    @JsonSerialize(using = MoneySerializer.class)
    @JsonDeserialize(using = MoneyDeserializer.class)
    public BigDecimal getAmount() {
        return amount;
    }
}

application.properties 或通过配置类 Jackson2ObjectMapperBuilderCustomizer 可以进行全局配置,避免在每个类上重复注解。

配置文件方式:

properties 复制代码
# 缩进输出,便于阅读
spring.jackson.serialization.indent-output=true
# 忽略未知属性,反序列化时如果JSON有类中没有的字段不报错
spring.jackson.deserialization.fail-on-unknown-properties=false
# 日期全局格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
# 忽略值为 null 的字段
spring.jackson.default-property-inclusion=non_null

配置类方式(Configuration):

java 复制代码
@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
        return builder -> {
            builder.serializationInclusion(JsonInclude.Include.NON_NULL);
            builder.featuresToEnable(SerializationFeature.INDENT_OUTPUT);
            builder.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
        };
    }
}
相关推荐
qq_12498707533 小时前
基于springboot的建筑业数据管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·毕业设计
IT_陈寒4 小时前
Vite 5.0实战:10个你可能不知道的性能优化技巧与插件生态深度解析
前端·人工智能·后端
z***3354 小时前
SQL Server2022版+SSMS安装教程(保姆级)
后端·python·flask
zxguan5 小时前
Springboot 学习 之 下载接口 HttpMessageNotWritableException
spring boot·后端·学习
爱分享的鱼鱼5 小时前
Spring 事务管理、数据验证 、验证码验证逻辑设计、异常回退(Java进阶)
后端
程序员西西5 小时前
Spring Boot中支持的Redis访问客户端有哪些?
java·后端
空白诗5 小时前
tokei 在鸿蒙PC上的构建与适配
后端·华为·rust·harmonyos
q***58195 小时前
SpringBoot返回文件让前端下载的几种方式
前端·spring boot·后端