前言
本文主要介绍在springboot项目后端开发过程中,如何将返回前端的响应数据进行序列化,从而:
- 统一处理null值、默认值
- 统一处理小数的保留位数
日期的序列化和统一格式,后续补充
此外,在稍微介绍一些序列化的入门知识。
文中已将json序列化的代码封装成工具类,要直接看代码的请直接下拉
主要参考博客:
注解方式自定义序列化(如果返回的值为空,则序列化为““)
深入理解Java中的序列化和反序列化
1. 入门知识
1.什么是序列化
java中,序列化是指将一个对象转换为字节序列的过程,以便在网络上传输或者将其持久化到硬盘等存储介质中。序列化可以将一个对象的状态转换为字节流,从而可以将其传输到另一个Java虚拟机或者将其持久化到硬盘上。简单的说:
序列化:将数据结构或对象转换成二进制字节流的过程
反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
2.为什么要序列化
序列化在Java中是非常重要的,因为它提供了一种机制,使得对象可以在网络上进行传输或者被持久化到硬盘等存储介质中。具体来说,序列化的必要性体现在以下几个方面:
远程方法调用(RMI): 序列化允许在不同的Java虚拟机之间进行对象传输,使得一个Java虚拟机上的对象可以调用另一个Java虚拟机上的方法。
Web服务: 序列化允许在Web服务中进行对象传输,使得不同的应用程序可以通过HTTP、SOAP等协议进行通信。
缓存: 序列化可以将对象存储在内存中,以便快速访问。
消息队列: 序列化允许在消息队列中进行对象传输,使得不同的应用程序可以通过发送和接收消息进行通信。
对象持久化: 序列化可以将对象持久化到硬盘等存储介质中,以便应用程序在关闭后仍然能够访问数据。
分布式系统: 序列化允许在分布式系统中进行对象传输,使得不同的计算机可以通过网络进行通信和协作。
因此,序列化在Java中是非常必要的,如果没有序列化,如果没有序列化会导致以下问题:
无法在网络上传输Java对象
无法将Java对象持久化到硬盘等存储介质中
无法在不同的Java虚拟机之间进行远程方法调用(RMI)
无法在不同的应用程序之间进行Web服务通信
2.代码:统一处理null值、默认值,统一处理小数的保留位数
定义序列化 需要继承 JsonSerializer类, 重写serialize方法。
主要思路:
- 利用反射获取要序列化的对象的所有属性名和属性值
- 判断:如果属性值为null,为不同类型的属性分别赋一个默认值,如:string类型赋"",Integer类型赋0,List集合赋空集等
- 判断:如果属性值不为null,定义其他处理规则,如:保留指定小数位、添加/删除list中的元素等
注意:小数点后的保留位数可以参考博客,此处不再赘述
1.工具类源码
代码中注释已经描述的比较清楚了,就不再赘述了。
特别说明:此处代码在参考 注解方式自定义序列化(如果返回的值为空,则序列化为““) 的代码的基础上,进行了补充和完善
package com.example.fastlearnspringboot.jsonUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
/**
* desperation:自定义json学历恶化工具类,处理null值,并将小数四舍五入保留2位。
* 要使用时,在要序列化的类上加上注解: @JsonSerialize(using = JacksonSerializer.class)
* 该类的对象的所有属性在序列化时都会执行serialize()方法中的操作
*/
public class EntityDefaultValueSerializer extends JsonSerializer<Object> {
public EntityDefaultValueSerializer() {
}
/**
*
* @param value :要序列化的对象
* @param gen :JsonGenerator对象
* @param provider
*/
@SneakyThrows
/**
* @SneakyThrows : 主要作用是帮助开发者在Java代码中消除异常处理模板代码。它允许开发者在被注解的方法中抛出当前方法实现的异常,并将其包装成RuntimeException,
* 使得调用点可以不用显示处理异常信息。这样,开发者就可以避免编写一些不必要的异常处理代码,使代码更加简洁易读。
*/
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider){
//获取类
Class<?> objectClass = value.getClass();
//获取类中的所有字段
Field[] declaredFields = objectClass.getDeclaredFields();
// 创建list的空对象,用于处理对象中可能存在的list属性为null的情况
List<Object> emptyList = new ArrayList<>();
// 创建Map的空对象,用于处理对象中可能存在的Map属性为null的情况
Map<Object, Object> emptyMap = new HashMap<>();
//开始序列化
gen.writeStartObject();
for (int i = 0; i < declaredFields.length; i++) {
//获取属性
Field declaredField = declaredFields[i];
//获取属性类型的简单名称
String typeName = declaredField.getType().getSimpleName();
//获取属性名称
String name = declaredField.getName();
//构造get方法用以获取属性值
String GET_METHOD = "get" + StringUtils.capitalize(name);
//获取成员方法 get方法
Method method = objectClass.getMethod(GET_METHOD);
//通过get方法获取值
Object invoke = method.invoke(value);
//如果属性值为空,序列化为默认值,各类型的默认值可自行修改
if (invoke == null) {
if (typeName.equals("String")) {
gen.writeStringField(name, ""); //String设为空串""
}else if (typeName.equals("Integer")) {
gen.writeNumberField(name, (Integer) 0); //整型设为0
}else if (typeName.equals("Double")) {
gen.writeNumberField(name, (Double) 0.0); //Double型设为0
}else if (typeName.equals("BigDecimal")) {
gen.writeNumberField(name, BigDecimal.ZERO); //BigDecimal型设为0
}else if (typeName.equals("List")) {
//列表型空值返回[]
// gen.writeObjectField(name, new ArrayList<>());
gen.writeObjectField(name, emptyList);
}else if (typeName.equals("Map")) {
//map型空值返回{}
// gen.writeObjectField(name, new HashMap<>());
gen.writeObjectField(name, emptyMap);
}else{
// 此处可添加其他类型的null值处理
continue;
}
}else { //否则正常序列化,或者赋初值
if (typeName.equals("String")) {
gen.writeStringField(name, String.valueOf(invoke));
} else if (typeName.equals("Integer")) {
gen.writeNumberField(name, (Integer) invoke);
} else if (typeName.equals("Double")) {
// 四舍五入保留2位小数, 法一:代码简介,但修改保留位数麻烦
Double doubleValue = (Double) invoke;
gen.writeNumberField(name, Math.round(doubleValue * 100.0) / 100.0); // 四舍五入保留2位小数
// 四舍五入保留2位小数, 法二:可读性好,要修改保留位数,直接修改setScale的第一个参数即可,但每次序列化都要new BigDecimal对象以及数据类型转换,造成不必要的开销
// BigDecimal bigDecimal = new BigDecimal((double) invoke);
// gen.writeNumberField(name, bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
} else if (typeName.equals("BigDecimal")) {
// 如果是 BigDecimal 类型,四舍五入保留4位小数
BigDecimal bigDecimal = (BigDecimal) invoke;
if (bigDecimal.compareTo(BigDecimal.ZERO) != 0) {
gen.writeStringField(name, bigDecimal.setScale(4, BigDecimal.ROUND_HALF_UP).toPlainString());
// gen.writeString(bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
} else {
gen.writeString(BigDecimal.ZERO.toPlainString());
}
} else {
//其他类型的序列化处理
continue;
}
}
}
//结束序列化
gen.writeEndObject();
}
}
2.工具类的使用
使用该工具类,只需在序列化的类名的上一行加上注解:
@JsonSerialize(using = EntityDefaultValueSerializer.class)
如图所示:
代码如下:
package com.example.fastlearnspringboot.entity;
import com.example.fastlearnspringboot.jsonUtil.EntityDefaultValueSerializer;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@Data // lombok插件注解,自动添加setter、getter方法
@NoArgsConstructor // lombok插件注解,无参构造方法
@AllArgsConstructor // lombok插件注解,全参构造方法
//@JsonInclude(JsonInclude.Include.NON_NULL) // 序列化时去掉所有值为null的属性
@JsonInclude(JsonInclude.Include.ALWAYS) // 序列化时保留所有值为null的属性
@JsonSerialize(using = EntityDefaultValueSerializer.class) //指定序列化类
public class TestJsonEntity {
private Long id;
private String name1;
private String name2 = "dsf";
private String name3;
private String name4;
private Double rate = 11.123456;
private Double rate2 = 11.123456;
private BigDecimal salary1 = BigDecimal.valueOf(111.123456);
private BigDecimal salary2 = BigDecimal.valueOf(0.000001);
private BigDecimal salary3;
private List<User> list;
private Map<String, String> map;
}
3.在Controller类的接口调用:
Controller 类中的代码如下:
package com.example.fastlearnspringboot.controller;
import com.example.fastlearnspringboot.entity.TestJsonEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/json")
public class JsonSerializeController {
@GetMapping("/nullToDefaultValue")
public TestJsonEntity nullToDefaultValue(){
TestJsonEntity testJsonEntity = new TestJsonEntity();
testJsonEntity.setName1("小小");
return testJsonEntity;
}
}
4.接口调用效果
左图:在 TestJsonEntity
类上没有添加@JsonSerialize
注解
右图:在 TestJsonEntity
类的上一行添加@JsonSerialize(using = EntityDefaultValueSerializer.class)
注解