SpringBoot的序列化和反序列化
序列化与反序列化
1、认识序列化与反序列化
Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。
2、为什么要实现对象的序列化和反序列化?
(1)我们创建的Java对象被存储在Java堆中,当程序运行结束后,这些对象会被JVM回收。但在现实的应用中,可能会要求在程序运行结束之后还能读取这些对象,并在以后检索数据,这时就需要用到序列化。
(2)当Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。
一个例子:
需要用到的User类
public class User{private String name;private int age;@Overridepublic 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 {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {FastJsonHttpMessageConverter fjc = new FastJsonHttpMessageConverter();FastJsonConfig fj = new FastJsonConfig();//字符类型字段如果为null,则输出"",而非nullfj.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();
SpringBoot的序列化和反序列化相关推荐
- springboot ObjectId 序列化 反序列化
springboot ObjectId 序列化,反序列化 1. 涉及方法和注解: @RequestBody @ResponseBody objectMapper.readValue() objectM ...
- SpringBoot 自定义Kafka消息序列化和反序列化
1. 概述 Kafka传输自定义的DTO对象时,不能像平时一样使用StringSerializer和StringDeserializer.这种情况需要自己实现对应DTO的序列化器和反序列化器.假设现在 ...
- 实现序列化与反序列化,一定要绕开这些坑!
今日推荐 这 9 个 Java 开源项目 yyds,你知道几个?阿里技术专家推荐的20本书,免费送!K8S 部署 SpringBoot 项目(一篇够用)妙用Java 8中的 Function接口 消灭 ...
- json 反序列化 父子类型_Jaskson精讲第7篇-类继承关系下的JSON序列化与反序列化JsonTypeInfo...
Jackson是Spring Boot(SpringBoot)默认的JSON数据处理框架,但是其并不依赖于任何的Spring 库.有的小伙伴以为Jackson只能在Spring框架内使用,其实不是的, ...
- 使用HttpMessageConverter实现HTTP的序列化和反序列化
对象的序列化/反序列化大家应该都比较熟悉:序列化就是将object转化为可以传输的二进制,反序列化就是将二进制转化为程序内部的对象.序列化/反序列化主要体现在程序I/O这个过程中,包括网络I/O和磁盘 ...
- java序列化_夯实Java基础系列22:一文读懂Java序列化和反序列化
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...
- 夯实Java基础系列22:一文读懂Java序列化和反序列化
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...
- Serializable详解(1):代码验证Java序列化与反序列化
说明:本文为Serializable详解(1),最后两段内容在翻译上出现歧义(暂时未翻译),将在后续的Serializable(2)文中补充. 介绍:本文根据JDK英文文档翻译而成,本译文并非完全按照 ...
- 一文读懂Json序列化与反序列化
一文读懂Json序列化与反序列化 一文读懂Json序列化与反序列化 #mermaid-svg-tVjnnlFu6ZBDpGOQ {font-family:"trebuchet ms" ...
最新文章
- JAVA基本字节类型
- Github 开源:使用控制器操作 WinForm/WPF 控件( Sheng.Winform.Controls.Controller)
- 图片去雾c语言代码,深度学习实现图像去雾
- 每日一学:如何读取网络图片
- 安徽工业大学计算机考研历年分数线,安徽工业大学历年考研分数线汇总[2012-2021]...
- 零基础转行Linux云计算运维工程师获得20万年薪的超级学习技巧
- Java-GuardedBlocks与BusyWaitting忙等待
- Premiere Pro After Effects插件开发调试方法
- build.gradle文件介绍
- 利用交换机搭建局域网并实现PC机或者带网口设备的通信
- yolo系列之yolo v3【深度解析】
- 软考-中级-网络工程师-知识点个人总结(三)
- python matplotlib 万花筒画板
- 把幽灵和熔断关闭_比较幽灵和三巨头
- Windows桌面美化(壁纸网站,任务栏透明、颜色设置)
- 怎么样禁止鼠标和键盘唤醒win7系统睡眠模式转载
- word中利用题注实现公式图表自动编号及引用
- 天玑9000+和骁龙8gen1+哪个性能更强 两者配置对比
- 计算机科学与技术张檬,清华大学计算机科学与技术系
- go语言基础之浮点数