SpringBoot的序列化和反序列化
序列化与反序列化
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 InputStream());
User user=(adObject();
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 OutputStream());
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。
反序列化的调⽤链如下:
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类中做如下定义:
@RequestMapping("/breedManagement/breedAnalysis")
public class BreedAnalysisController {
@Resource(name="BreedAnalysisService")
private BreedAnalysisService breedAnalysisService;
@RequestMapping("/getBaseInfo")
public JsonResult getBaseInfo(@RequestBody HashMap<String,Object> map){
BaseInfo(map);
}
}
在 Controller 中使⽤@ResponseBody注解即可返回 Json 格式的数据,⽽@RestController注解包含了@ResponseBody 注解,所以默认情况下,@RestController即可将返回的数据结构转换成Json格式。
这些注解之所以可以进⾏Json与JavaBean之间的相互转换,就是因为HttpMessageConverter发挥着
作⽤。
org.verter.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:引⼊依赖
<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.("numList").toString(),List.class);
System.out.(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;
}
// 判断数字是否为整数值springboot框架的作用
long lngNum = (long) dbNum;
if (dbNum == lngNum) {
return lngNum;
} else {
return dbNum;
}
step2:修改Gson的适配器为⾃定义的
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = ate();
long lngNum = (long) dbNum;
if (dbNum == lngNum) {
return lngNum;
} else {
return dbNum;
}
step2:修改Gson的适配器为⾃定义的
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = ate();

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。