Serialization(序列化)是一种将对象以一连串的字节描述的过程;deserialization(反序列化)是一种将这些字节重建成一个对象的过程。将程序中的对象,放入文件中保存就是序列化,将文件中的字节码重新转成对象就是反序列化。
总结,在网络中数据的传输必须是序列化形式来进行的。
它的 writeObject(Object obj) 方法可以对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。
它的 readObject() 方法从源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。
只有实现了 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 对象反序列化成功!
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致,这个所谓的序列化 ID,就是我们在代码中定义的 serialVersionUID。
serialVersionUID 得生成方法:
在项目开发中,我们的类并没有实现 Serializable 接口,实际上这是 Spring 框架帮我们做了一些事情,Spring 并不是直接把 User 对象进行网络传输,而是把 User 对象转换成 json 格式的字符串,然后再进行传输的,而 String 类实现了 Serializable 接口并且显示指定了 serializableUID
public final class String
implements java.io.Serializable, Comparable, 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> 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 {
//判断当前转换器是否可以解析前端传来的数据
boolean canRead(Class> clazz, MediaType mediaType);
//判断当前转换器是否可以将后端数据解析为前端需要的格式
boolean canWrite(Class> clazz, MediaType mediaType);
//获取当前转换器可以解析的数据类型
List 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
com.alibaba
fastjson
1.2.70
@Configuration
public class MyWebmvcConfiguration implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List> converters) {
FastJsonHttpMessageConverter fjc = new FastJsonHttpMessageConverter();
FastJsonConfig fj = new FastJsonConfig();
//字符类型字段如果为null,则输出"",而非null
fj.setSerializerFeatures(SerializerFeature.WriteNullStringAsEmpty);
fjc.setFastJsonConfig(fj);
// 将该自定义转换器排在第一个,使其生效
converters.add(0, fjc);
}
}
属性名称 | 解释 |
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
{
"id": "11",
"name": null
}
配置后:如果字符串类型为 null 的话,输出是为 ""
{
"id": "11",
"name": null
}
原因:项目中同时存在了 WebMvcConfigurationSupport 和 WebMvcConfigurer 这两个配置,只有该 WebMvcConfigurationSupport 的配置生效
解决办法:将 WebMvcConfigurationSupport 改造成 WebMvcConfigurer。因为多个WebMvcConfigurer 的配置的话,都是会生效的
原因:由于 converters 是 HttpMessageConverter 的列表list,而新 add 的消息转换器位于列表的最后,所以可能不生效
解决办法:可以使用列表 list 的 add(0, object),converters.add(0,fastJsonHttpMessageConverter),把 fastJsonHttpMessageConverter 插入列表头