【SpringBoot】序列化和反序列化介绍

一、认识序列化和反序列化

        Serialization(序列化)是一种将对象以一连串的字节描述的过程;deserialization(反序列化是一种将这些字节重建成一个对象的过程。将程序中的对象,放入文件中保存就是序列化,将文件中的字节码重新转成对象就是反序列化。

二、为什么要实现序列化和反序列化

  • 我们创建的 Java 对象被存储在 Java 堆中,当程序运行结束后,这些对象会被 JVM 回收。但在现实的应用中,可能会要求在程序运行结束之后还能读取这些对象,并在以后检索数据,这时就需要用到序列化。
  • 当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。而 java 是面向对象的开发方式,一切都是 java 对象,想要实现 java 对象的网络传输,就可以使用序列化和反序列化来实现。发送方将需要发送的 Java 对象序列化转换为字节序列,然后在网络上传送;接收方接收到字符序列后,使用反序列化从字节序列中恢复出 Java 对象。

总结,在网络中数据的传输必须是序列化形式来进行的。

三、序列化和反序列化的实现

1、JDK 类库提供的序列化 API

  • java.io.ObjectOutputStream:表示对象输出流

        它的 writeObject(Object obj) 方法可以对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。

  • java.io.ObjectInputStream:表示对象输入流

        它的 readObject() 方法从源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。

2、实现序列化的要求

只有实现了 Serializable 或 Externalizable 接口的类的对象才能被序列化,否则抛出异常。

public class SerializableTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        serializeStudent();
        deserializeStudent();
    }

    // JDK 类库中序列化的步骤
    static void serializeStudent() throws IOException, ClassNotFoundException {
        
        // 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流:
        FileOutputStream fos = new FileOutputStream("F:\\HaC.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Student student1 = new Student("HaC", "HelloCoder", 30);
        
        // 通过对象输出流的 writeObject() 方法写对象
        oos.writeObject(student1);
        oos.flush();
        System.out.println("Student 对象序列化成功!");
        oos.close();
    }

    // JDK 类库中反序列化的步骤
    static void deserializeStudent() throws IOException, ClassNotFoundException {
        
        // 创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
        FileInputStream fis = new FileInputStream("F:\\HaC.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);

        // 通过对象输出流的 readObject() 方法读取对象:
        Student student2 = (Student) ois.readObject();
        System.out.println(student2.getUserName() + " " +
                student2.getPassword() + " " + student2.getYear());
        System.out.println("Student 对象反序列化成功!");
    }
}

@Data
@AllArgsConstructor
class Student implements Serializable {
    private static final long serialVersionUID = 3608451818006447637L;
    private String userName;
    private String password;
    private String year;
}

可以看到生成了一个打开是乱码的二进制文件:

其实这个例子就是序列化和反序列化的一个小过程,JVM 通过序列化把对象写到文件,再通过反序列化从文件中读取数据,把数据转成一个对象。

看到控制台输出也是正常的:

Student 对象序列化成功!
HaC HelloCoder 30
Student 对象反序列化成功!

四、serialVersionUID 的作用

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致,这个所谓的序列化 ID,就是我们在代码中定义的 serialVersionUID。

serialVersionUID 得生成方法:

  • private static final long serialVersionUID = 1L;
  • 根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个 64 位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final long serialVersionUID = xxxxL。显示声明 serialVersionUID 可以避免对象不一致
  • 如果没有显示的定义 serialVersionUID 变量的时候,JAVA 序列化机制会根据 Class 自动生成一个 serialVersionUID 作序列化版本比较用,这种情况下,如果 Class 文件 (类名,方法名等) 没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID 也不会变化的。

五、SpringBoot 中的序列化和反序列化

在项目开发中,我们的类并没有实现 Serializable 接口,实际上这是 Spring 框架帮我们做了一些事情,Spring 并不是直接把 User 对象进行网络传输,而是把 User 对象转换成 json 格式的字符串,然后再进行传输的,而 String 类实现了 Serializable 接口并且显示指定了 serializableUID

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    private final char value[];

    private int hash; // Default to 0

    private static final long serialVersionUID = -6849794470754667710L;

Json 是一种轻量级的文本数据交换格式,在 Json 字符串中 {} 用来表示对象,[ ] 用来表示列表,数据以 key:value 的形式存放,如:

{
	"name":"zhangsan",
	"age":"22",
	"course":["java","python"]
}

 在 SpringBoot 中,想要一个接口接收 Json 格式的数据并返回 Json 格式的数据,前端将 http 请求头 “Accept” 设置为 “application/json”,Content-Type 为 "application/json"

中间件只需要在 Controller 类中做如下定义:

@RestController
@RequestMapping("/equity")
public class EquityExpose {

    @PostMapping("/list")
    @ApiOperation(value = "权益列表", notes = "权益列表")
    public ApiResultResponse<GetEquityResponse>> list(@RequestBody GetEquityRequest request) {
        return service.list(request);
    }

}

在 Controller 中使用 @ResponseBody 注解即可返回 Json 格式的数据,而 @RestController 注解包含了 @ResponseBody 注解,所以默认情况下,@RestController 即可将返回的数据结构转换成 Json 格式。

这些注解之所以可以进行 Json 与 JavaBean 之间的相互转换,就是因为 HttpMessageConverter 发挥着作用。

org.springframework.http.converter.HttpMessageConverter 是一个策略接口,是 Http request 请求和 response 响应的转换器,该接口只有五个方法,它的 canRead() 方法返回 true,然后它的 read() 方法会从请求中读出请求参数,绑定到 readString() 方法的 string 变量中。

当 SpringMVC 执行 readString 方法后,由于返回值标识了 @ResponseBody,SpringMVC 将使用 StringHttpMessageConverter 的 write() 方法,将结果作为 String 值写入响应报文,当然,此时 canWrite() 方法返回 true。

public interface HttpMessageConverter<T> {
 
	//判断当前转换器是否可以解析前端传来的数据
	boolean canRead(Class<?> clazz, MediaType mediaType);
	
	//判断当前转换器是否可以将后端数据解析为前端需要的格式
	boolean canWrite(Class<?> clazz, MediaType mediaType);
 
	//获取当前转换器可以解析的数据类型
	List<MediaType> getSupportedMediaTypes();
 
	//读取前端传来的数据
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;
 
	//将后台数据转换,返回给前端
	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;
 }

流程图如下:

前端发来请求后,先调用 HttpInputMessage 从输入流中获取 Json 字符串,然后在 HttpMessageConverter 中把 Json 转换为接口需要的形参类型。

六、定制化

当出现特定的需求时,比如:此时需要自定义自己的消息转换器,可以使用 Spring 或者第三方提供的 HttpMessageConverter 如(FastJson,Gson, Jackson)

问题引入字符类型字段为 null 时,输出为 “”,而不是 null

1、引入依赖

<dependency>
	<groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>

2、对 FastJsonHttpMessageConverter 进行配置

@Configuration
public class MyWebmvcConfiguration implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter fjc = new FastJsonHttpMessageConverter();
        FastJsonConfig fj = new FastJsonConfig();
     
        //字符类型字段如果为null,则输出"",而非null
        fj.setSerializerFeatures(SerializerFeature.WriteNullStringAsEmpty);
        fjc.setFastJsonConfig(fj);
        
        // 将该自定义转换器排在第一个,使其生效
        converters.add(0, fjc);
    }
}

3、SerializerFeature 配置属性的解释

属性名称解释
QuoteFieldNames输出key时是否使用双引号,默认为true
UseSingleQuotes使用单引号而不是双引号,默认为false
WriteMapNullValue是否输出值为null的字段,默认为false。应用场景:前端必须需要所有字段
UseISO8601DateFormatDate使用ISO8601格式输出,默认为false
WriteNullListAsEmptyList字段如果为null,输出为[],而不是null
WriteNullStringAsEmpty字符类型字段如果为null,输出为"",而不是null
WriteNullNumberAsZero数值字段如果为null,输出为0,而非null
WriteNullBooleanAsFalseBoolean字段如果为null,输出为false,而非null
SkipTransientField如果是true,类中的Get方法对应的Field是transient,序列化时将会被忽略。默认为true
SortField按字段名称排序后输出。默认为false

配置前:如果字符串类型为 null 的话,输出是为 null

{
    "id": "11",
    "name": null
}

 配置后:如果字符串类型为 null 的话,输出是为 ""

{
    "id": "11",
    "name": null
}

七、序列化及反序列化相关知识

  • 在Java中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。
  • 通过 ObjectOutputStream ObjectInputStream 对对象进行序列化及反序列化。
  • 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID )。
  • 序列化并不保存静态变量。
  • 要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。
  • Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

八、过程中遇到的问题及解决办法

1、FastJsonHttpMessageConverter 不生效,没有注入到 Spring 容器中

原因:项目中同时存在了 WebMvcConfigurationSupport 和 WebMvcConfigurer 这两个配置,只有该 WebMvcConfigurationSupport 的配置生效

解决办法:将 WebMvcConfigurationSupport 改造成 WebMvcConfigurer。因为多个WebMvcConfigurer 的配置的话,都是会生效的

2、FastJsonHttpMessageConverter 注入到 Spring 容器中,但是不起作用

原因:由于 converters 是 HttpMessageConverter 的列表list,而新 add 的消息转换器位于列表的最后,所以可能不生效

解决办法:可以使用列表 list 的 add(0, object),converters.add(0,fastJsonHttpMessageConverter),把 fastJsonHttpMessageConverter 插入列表头

九、注意事项

  1. 注意区分 configureMessageConverters 和 extendMessageConverters方 法的不同,前者会覆盖掉原有的消息转换器集合,而只保留当前的集合,因此如果使用了这个方法,就会覆盖掉默认的消息转换器集合,因此这里得注意配置了新的会不会引起功能的缺失,比如说默认的实际上是支持基本的 @RequestBody,@ResponseBody 功能的,配置了新的也要支持,不能让这两个注解失效。如果担心的话,可以使用 extendMessageConverters 方法配置消息转换器,这样就不会覆盖,确保了安全。
  2. @Bean 和 写在 WebMvcConfigurer 里面的区别是,注入bean的方式,这种方法加入的转换器排序是第一位。实现 WebMvcConfigurer 接口,这种方法加入的转换器排序是最后一位。

十、参考文档

  • WebMvcConfigurationSupport 和 WebMvcConfigurer 区别和同时使用产生的问题-解决
  • SpringBoot的序列化和反序列化  
  • spring-boot2.x,使用EnableWebMvc注解导致的自定义HttpMessageConverters不可用


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/149625.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

220V交流转直流的简易电源设计

220V交流转直流的简易电源设计 设计简介设计原理电路图变压器电路交流转直流电路3.3V电源接口电路 PCB3D图 实践检验 设计简介 通过模拟电路的相关知识&#xff0c;尝试将220V的交流电转化为我们指定电压的直流电。 设计原理 将220V交流电转化为直流电的方法常用的有通过变压器…

京东数据挖掘(京东数据采集):2023年Q3电脑行业数据分析报告

近年来&#xff0c;在远程办公、远程教育等需求的刺激下&#xff0c;电脑的销售增长较为显著。不过&#xff0c;随着市场的成熟乃至饱和&#xff0c;电脑销售市场也逐渐出现增长困难、需求疲软等问题。 2023年第三季度&#xff0c;电脑市场的出货量同比下滑。根据鲸参谋电商数据…

lc121. 买卖股票的最佳时机

一次遍历&#xff0c;一边遍历一边修改买入的价格&#xff0c;一边比较取得最大利润 public class BuyAndSellStocks {public static void main(String[] args) {int[] arr {7,1,5,3,6,4};int[] arr1 {7,6,4,3,1};System.out.println(buyAndSellStocks(arr));System.out.pri…

【开发问题解决方法记录】01.dian

一些问题记录 新增角色失败&#xff1a;Error: Ajax 调用为Execute Server-Side Code返回了服务器错误ORA-01722: 无效数字。 【问题原因】&#xff1a;CREATE_BY(NUMBER类型)应该存入USER_ID(NUMBER类型)而非USER_NAME&#xff08;NVARCHAR2类型&#xff09; 【解决方法】将…

碾压Fast Request!IDEA插件推荐:Apipost-Helper

IDEA是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它可以帮助开发人员更加高效地编写、调试和部署软件应用程序。我们在编写完接口代码后需要进行接口调试等操作&#xff0c;一般需要打开额外的调试工具&#xff0c;而今天给大家介绍一款IDEA插件&…

第十九章绘图

Java绘图类 Graphics 类 Grapics 类是所有图形上下文的抽象基类&#xff0c;它允许应用程序在组件以及闭屏图像上进行绘制。Graphics 类封装了Java 支持的基本绘图操作所需的状态信息&#xff0c;主要包括颜色、字体、画笔、文本、图像等。 Graphics 类提供了绘图常用的方法&a…

阿里云轻量级服务器搭建

购买服务器 服务器分类 轻量应用服务器 阿里云ECS 购买轻量应用服务器 实例类型 地域和可用区 *这里选择的是服务器的所在地&#xff0c;有国外和国内的 镜像 系统镜像 *系统镜像就是服务器自身的系统类型 *这里建议选择Centos7.x版本的 应用镜像 *用镜像就是在系统…

C语言——分割单向链表

本文的内容是使用C语言分割单向链表&#xff0c;给出一个链表和一个值&#xff0c;要求链表中小于给定值的节点全都位于大于或等于给定值的节点之前&#xff0c;打印原始链表的所有元素和经此操作之后链表的所有元素。 分析&#xff1a;本题只是单向链表的分割&#xff0c;不涉…

JavaScript 语句、标识符、变量

语句 JavaScript程序的单位是行(line),也就是一行一行地执行。一般情况下&#xff0c;每一行就是一个语句 var num 10; 语句以分号结尾&#xff0c;一个分号就表示一个语句结束。 标识符 标识符(identifier)指的是用来识别各种值的合法名称。最常见的标识符就是变量名标识符…

如何快速将钉钉员工信息同步到飞书

当企业内部在使用钉钉跟飞书时&#xff0c;那么当钉钉员工信息发生更改时&#xff0c;我们应该如何将信息快速同步到飞书上呢&#xff0c;接下来我们借助RestCloud AppLink平台进行演示。 第一步&#xff1a;获得钉钉以及飞书认证授权 钉钉授权 钉钉接入采用自建应用的方式&…

二维码智慧门牌管理系统升级解决方案:流量监控引领服务卓越

文章目录 前言一、流量监控功能概述二、流量监控的益处三、应用案例和成功故事四、实施和支持 前言 随着科技的不断发展&#xff0c;二维码智慧门牌管理系统在其便捷高效的管理方式下&#xff0c;深受广大用户喜爱。为了更好地满足用户需求&#xff0c;提升服务质量&#xff0…

Python OpenCV 通过trackbar调整图像亮度对比度颜色

上一篇文章通过设置固定值的方式来调整图像&#xff0c;这篇文章通过trackbar来动态调整参数&#xff0c;从而实时展现图像处理结果&#xff0c;得到想要的图像处理参数。 1. 创建trackbar import cv2 import numpy as npdef nothing(x):passcv2.namedWindow(image) # 创建5个…

永磁材料测试系统主要应用

1. 产品特征 永磁材料测试系统装置具有独立的电参量校准功能。采用慢速减幅方式对样品退磁。超宽范围的电流连续稳定调节。磁通计的积分器零漂和霍尔探头的非线性误差影响小。系统配置连续可调双极性磁化电源&#xff0c;方便样品的磁化与退磁。测量B或J&#xff1a;采用B或J线…

线程安全问题及其解决

文章目录 一. 线程安全问题1.1 线程不安全的例子1.2 线程不安全的原因1.2.1 随即调度, 抢占式执行1.2.2 修改共享数据1.2.3 修改操作非原子性1.2.4 内存可见性1.2.5 指令重排序1.2.6 总结 二. 线程安全问题的解决2.1 synchronized(解决前三个问题)2.1.1 synchronized 的锁是什么…

基于PyTorch搭建你的生成对抗性网络

前言 你听说过GANs吗&#xff1f;还是你才刚刚开始学&#xff1f;GANs是2014年由蒙特利尔大学的学生 Ian Goodfellow 博士首次提出的。GANs最常见的例子是生成图像。有一个网站包含了不存在的人的面孔&#xff0c;便是一个常见的GANs应用示例。也是我们将要在本文中进行分享的…

Apache Pulsar 技术系列 - 基于 Pulsar 的海量 DB 数据采集和分拣

导语 Apache Pulsar 是一个多租户、高性能的服务间消息传输解决方案&#xff0c;支持多租户、低延时、读写分离、跨地域复制、快速扩容、灵活容错等特性。本文是 Pulsar 技术系列中的一篇&#xff0c;主要介绍 Pulsar 在海量DB Binlog 增量数据采集、分拣场景下的应用。 前言…

阿里影业S1财报解读:优质内容叠加整合效益,转动增长飞轮

从《消失的她》到《长安三万里》再到《孤注一掷》&#xff0c;市场对阿里影业半年报好成绩已有所预期。 11月13日&#xff0c;阿里影业发布2023/24半年度业绩。根据财报&#xff0c;报告期内&#xff08;4月1日至9月30日&#xff09;&#xff0c;阿里影业实现收入人民币26.16亿…

深入理解SqueezeSegV3点云分割

文章&#xff1a;Squeezesegv3: Spatially-adaptive convolution for efficient point-cloud segmentation 代码&#xff1a;https://github.com/chenfengxu714/SqueezeSegV3 一、摘要 激光雷达点云分割是许多应用中的一个重要问题。对于大规模点云分割&#xff0c;一般是投…

【算法每日一练]-图论(保姆级教程 篇2(topo排序,并查集,逆元))#topo排序 #最大食物链 #游走 #村村通

今天讲topo排序 目录 题目&#xff1a;topo排序 思路&#xff1a; 题目&#xff1a;最大食物链 解法一&#xff1a; 解法二&#xff1a; 记忆化 题目&#xff1a;村村通 思路&#xff1a; 前言&#xff1a;topo排序专门处理DAG&#xff08;有向无环图&#xff09; 题目…