SpringBoot的序列化和反序列化

SpringBoot的序列化和反序列化序列化与反序列化1、认识序列化与反序列化Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。2、为什么要实现对象的序列化和反序列化?(1)我们创建的Java对象被存储在Java堆中,当程序运行结束后,这些对象会被JVM回收。但在现实的应用中,可能会要求在程序运行结束之后还能读取这些对象,并在以后检索数据,这时就需要用到序列化。(2)当Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送

大家好,又见面了,我是你们的朋友全栈君。

序列化与反序列化

1、认识序列化与反序列化

Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。

2、为什么要实现对象的序列化和反序列化?

(1)我们创建的Java对象被存储在Java堆中,当程序运行结束后,这些对象会被JVM回收。但在现实的应用中,可能会要求在程序运行结束之后还能读取这些对象,并在以后检索数据,这时就需要用到序列化。

(2)当Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。

一个例子:

需要用到的User类

public class User{
    private String name;
    private int age;
    
    @Override
    public String toString() {
        return "User{" +
                "name='"+name+'\''+
                ",age="+age+
                '}';
    }
}

Server类:

public class Server {
    public static void main(String[] args) throws IOException,ClassNotFoundException {
        ServerSocket serverSocket=null;

        serverSocket=new ServerSocket(8080);
        Socket socket = serverSocket.accept();
        ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());

        User user=(User)objectInputStream.readObject();
        System.out.println(user);
    }
}

Client类:

public class Client {
    public static void main(String[] args)throws IOException {
        Socket socket = null;
        socket = new Socket("localhost",8080);

        User user=new User();
        user.setAge(22);
        user.setName("zhangsan");

        ObjectOutputStream out= new ObjectOutputStream(socket.getOutputStream());
        out.writeObject(user);
        socket.close();
    }
}

上述代码模拟两个进程进行通信,代码运行以后,发现程序报错,并不能实现Java对象的正常传输,因为没有实现User类的序列化。

3、序列化与反序列化的实现

被序列化的对象需要实现java.io.Serializable接口,该接口只是一个标记接口,不用实现任何方法。

JDK提供了Java对象的序列化方式实现对象序列化传输,主 要通过输出流java.io.ObjectOutputStream和对象输入流java.io.ObjectInputStream来实现。

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

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

4、serialVersionUID 的作用

在这里插入图片描述

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

反序列化的调用链如下:

ObjectInputStream.readObject -> readObject0 -> readOrdinaryObject -> readClassDesc -> readNonProxyDesc -> 
ObjectStreamClass.initNonProxy

在initNonProxy中的关键代码如下:在反序列化的过程中,对serialVersionUID做了比较,如果发现不相等,则直接抛出异常。

在这里插入图片描述

serialVersionUID的生成方法:

(1)private static final long serialVersionUID = 1L;

(2)根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final long serialVersionUID = xxxxL;
显示声明serialVersionUID可以避免对象不一致

(3)如果没有显示的定义serialVersionUID变量的时候,JAVA序列化机制会根据Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果Class文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID也不会变化的。

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

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

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

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

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

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

@RestController
@RequestMapping("/breedManagement/breedAnalysis")
public class BreedAnalysisController {

    @Resource(name="BreedAnalysisService")
    private BreedAnalysisService breedAnalysisService;

    @RequestMapping("/getBaseInfo")
    public JsonResult getBaseInfo(@RequestBody HashMap<String,Object> map){
        return breedAnalysisService.getBaseInfo(map);
    }
}

在 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;
 }

Spring为HttpMessageConverter接口提供了多个实现类,在启动时会自动配置一些消息转换器,包括MappingJackson2HttpMessageConverter。
在这里插入图片描述

流程图如下:

在这里插入图片描述

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

在这里插入图片描述

6、定制化

当出现特定的需求时,比如:。此时需要自定义自己的消息转换器,有两种方式

方式一 使用Spring或者第三方提供的HttpMessageConverter(如FastJson,Gson,Jackson)

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

step1:引入依赖

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

step2:对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(fjc);
    }
}

SerializerFeature配置属性的解释

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

配置前:默认不输出为null的字符型字段

在这里插入图片描述

配置后:字符类型字段如果为null,输出为””

在这里插入图片描述

方式二 重写TypeAdapter

问题引入:在使用Gson将HashMap<String,Object>中的结果反序列化时,发现Integer类型自动转成了Double类型。示例代码如下:

	@RequestMapping("/convert")
    public void converter(@RequestBody HashMap<String,Object> map) {
        Gson gson=new Gson();
        List<Integer> numList =gson.fromJson(map.get("numList").toString(),List.class);
        System.out.println(numList.get(0));
    }

这是因为在反序列化的过程中,Gson会根据待解析的类型定位到具体的TypeAdaptor类,并通过该类的read方法组装成最后的对象,由于Map对应的是Object,这里的Gson最终定位到内置的ObjectTypeAdaptor类,该类的关键代码如下:我们可以看到,数值类型(NUMBER)全部被转换成了Double类型。

在这里插入图片描述

在这种情况下,可以使用DecimalFormat进行转换,也可以重写TypeAdapyter。

step1:重写TypeAdapter中的read方法,主要是修改数字的处理逻辑

 case NUMBER:
 	/**
    * 改写数字的处理逻辑,将数字值分为整型与浮点型。
    */
    double dbNum = in.nextDouble();

    // 数字超过long的最大值,返回浮点类型
    if (dbNum > Long.MAX_VALUE) {
    	return dbNum;
    }

     // 判断数字是否为整数值
     long lngNum = (long) dbNum;
     if (dbNum == lngNum) {
     	return lngNum;
     } else {
     	return dbNum;
     }

step2:修改Gson的适配器为自定义的

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(List.class,new DataTypeAdapter());
Gson gson = gsonBuilder.create();
 long lngNum = (long) dbNum;
 if (dbNum == lngNum) {
 	return lngNum;
 } else {
 	return dbNum;
 }

step2:修改Gson的适配器为自定义的

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(List.class,new DataTypeAdapter());
Gson gson = gsonBuilder.create();


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/148995.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • ElasticSearch分布式搜索引擎安装教程

    ElasticSearch分布式搜索引擎安装教程ElasticSearch分布式搜索引擎安装教程​专注小Du博客,每天分享干货知识。CSDN博客地址:在正式开始安装教程之前,小Du先带大家来了解什么是ElasticSearch。一.Hr:ElasticSearch是什么?​答:Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTfulweb接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级

  • 机器学习算法(一):逻辑回归模型(Logistic Regression, LR)[通俗易懂]

    机器学习算法(一):逻辑回归模型(Logistic Regression, LR)[通俗易懂]线性分类器:模型是参数的线性函数,分类平面是(超)平面;非线性分类器:模型分界面可以是曲面或者超平面的组合。典型的线性分类器有感知机,LDA,逻辑斯特回归,SVM(线性核);典型的非线性分类器有朴素贝叶斯(有文章说这个本质是线性的,http://dataunion.org/12344.html),kNN,决策树,SVM(非线性核)https://www.cnblogs.com/sparkw…

  • java关键字:fianl的一些简单的用法

    java关键字:fianl的一些简单的用法

  • eureka集群配置_hadoop高可用集群搭建

    eureka集群配置_hadoop高可用集群搭建Eruka高可用(集群)EurekaServer高可用配置(1)高可用是什么?“高可用性”(HighAvailability)通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性如:给EurekaServer搞一个备份(2)服务同步原理多个EurekaServer之间也会互相注册为服务,当服务提供者注册到EurekaServer集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到EurekaServer集

  • 嵌入式工程师岗位要求_fae和研发哪个工资高

    嵌入式工程师岗位要求_fae和研发哪个工资高FAE,嵌入式行业苦涩的职位FAE,也叫现场技术支持工程师大学招聘季的时候,憨娃娃一个,一心想找个嵌入式的职位,幻想着成为IronMan,左焊电路,右敲代码,打造属于自己的一身铁皮,成为村里走出去最科技的那个仔。现在想来,着实可笑了些。佩剑尚未佩好,出门就掉巨坑。“你要去的部门是我们公司最核心的部门,我们公司的主营业务都在那边。”HR的自豪我现在竟然还记得。“看起来我还是蛮优秀的嘛。”…

    2022年10月27日
  • vue 键盘事件keyup/keydoen

    vue 键盘事件keyup/keydoen使用:当你按下键盘,键盘的值为13的时候,将会执行show函数方法以上两种keyup和keyup.13方法是一样的,点击回车按键,就会执行alert方法一些便捷方式:@keyup.13回

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号