日拱一卒无有尽,功不唐捐终入海。

楔子

前两周发了三篇SpringSecurity一篇征文,这周打算写点简单有用易上手的文章,换换脑子,休息一下。

今天要写的是这篇:从LocalDateTime序列化来看全局一致性序列化体验

这个标题看起来蛮不像人话的,有种挺官方的感觉,我先给大家翻译翻译我们的主题是什么:通过讲解LocalDateTime的序列化从而引出整个项目中的所有序列化处理,并让他们保持一致。

在我们项目中一般存在着两种序列化,

一个呢是SpringMVC官方的序列化,也就是Spring帮你做的序列化,比如你在一个接口上面打了一个ResponseBody注解,SpringMVC中的消息转换器会帮你做序列化。

另一个就是我们项目内的序列化,自己定义的JsonUtil也好,还是你引入的第三方JSON处理工具(比如FastJson)也好,都可以说做是我们项目内部的序列化。

这两者如果不一样,有时候序列化出来的数据可能会出现结果不大一样的结果,为了防止这种情况,今天我们就来探讨一下项目中的序列化。

1. 举个例子

我们先来举个例子,来看看如果序列化不一致会出现啥样的效果。

@GetMapping("/api/anon")
public ApiResult test01() {
return ApiResult.ok("匿名访问成功");
}

这是一段很普通的访问接口,返回的结果如下:

{
"code": 200,
"msg": "请求成功",
"data": {
"请求成功": "匿名访问成功"
},
"timestamp": "2020-07-19T23:07:07.738",
"fail": false,
"success": true
}

这里大家只需要注意一下timestamp的序列化结果,timestamp是一个LocalDateTime类型,在SpringMVC中的消息转换器对LocalDateTime做序列化的时候没有特殊处理,直接调用了LocalDateTime的**toString()**方法,所以这个序列化结果中间有个T

但是如果这里的序列化用了其他方案,可能这个序列化结果会是不一样的体验,在我的项目中我也采用了Jackson来做序列化(Spring中也用的它),我们可以看看我们自己定义的一个JsonUtil对LocalDateTime做序列化会是什么结果。

@Slf4j
public class JacksonUtil { public static ObjectMapper objectMapper = new ObjectMapper(); /**
* Java对象转JSON字符串
*
* @param object
* @return
*/
public static String toJsonString(Object object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
log.error("The JacksonUtil toJsonString is error : \n", e);
throw new RuntimeException();
}
}
}

我们序列化工具类长这样,和上面一样,我们序列化一个ApiResult看看会是什么结果:

{
"code": 400,
"msg": "请求失败",
"timestamp": {
"month": "JULY",
"year": 2020,
"dayOfMonth": 19,
"hour": 23,
"minute": 25,
"monthValue": 7,
"nano": 596000000,
"second": 2,
"dayOfYear": 201,
"dayOfWeek": "SUNDAY",
"chronology": {
"id": "ISO",
"calendarType": "iso8601"
}
},
"fail": true,
"success": false
}

Jackson默认的ObjectMapper下序列化出来的结果就是这个鬼样子,因为是序列化最后倒是转化成字符串了,那这样的数据前端如果拿到了肯定是不能正常转成时间类型的,

LocalDateTime只是一个缩影,哪怕对于字符串,不同的序列化配置也是有着不同的影响,字符串里面可能会有转义字符,有引号,不同的方案出来的结果可能是不一样的,

在实际项目中对第三方接口进行HTTP对接一般来说都是需要的,其中传输过去的数据一般会经过我们项目中JSON工具类的序列化为字符串之后再传输过去,如果序列化方案不同可能会在序列化过程中传过去的数据不是我们想要的。

还有些接口是我们直接往HttpServeletResponse里面写数据,这种时候一般也是写JSON数据,比如:

public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setHeader("Cache-Control", "no-cache");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().println(JacksonUtil.toJsonString(ApiResult.fail(authException.getMessage())));
response.getWriter().flush();
}

这里我用工具类直接去序列化这个ApiResult,传给前台的数据就会也出现上面例子中的情况,LocalDateTime序列化结果不是我们想要的。

所以在项目中的序列化和Spring中的序列化保持一致还是很有必要的。

2. 实操方案

上面说过了项目中保持序列化的一致性的必要性(我认为是必要的哈哈)。

那我们下面就可以说说如果去做这个一致性。

我们知道,如果你想要在Spring的序列化中将你返回的那个对象某个LocalDateTime类型变量进行序列化的话,很简单,可以这样:

public class ApiResult implements Serializable {

    private static final Map<String, String> map = new HashMap<>(1);
private int code;
private String msg;
private Object data;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime timestamp;

就很简单的在这个变量上面加一个JsonFormat注解就ok了,但这样不是全局的,哪个变量加哪个变量就生效。

想做到全局生效,我们需要在Spring的配置去修改Spring中使用的ObjectMapper,了解Jackson的小伙伴应该都知道,序列化的各种配置都在配置在这个ObjectMapper中的,不知道也没关系,你现在知道了。

那么我们可以通过去配置Spring中的ObjectMapper做到全局生效:

@Configuration
public class JacksonConfig { @Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.locale(Locale.CHINA);
builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss"); JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); builder.modules(javaTimeModule);
};
}
}

通过在Jackson2ObjectMapperBuilderCustomizer之中加入一些序列化方案就可以达到这个效果,上文的代码就是做了这些操作,这样之后我们再次访问最开始那个接口,就会出现如下效果:

{
"code": 200,
"msg": "请求成功",
"data": {
"请求成功": "匿名访问成功"
},
"timestamp": "2020-07-20 00:06:12",
"fail": false,
"success": true
}

timestamp中间那个T不存在了,因为我们已经加入了LocalDateTime的序列化方案了。

但是仅仅如此还不行,这只是做了LocalDateTime的全局序列化,我们还需要让自己的工具类也和Spring的保持一致:

    @Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder)
{
ObjectMapper objectMapper = builder.createXmlMapper(false).build(); // 通过该方法对mapper对象进行设置,所有序列化的对象都将按改规则进行系列化
// Include.Include.ALWAYS 默认
// Include.NON_DEFAULT 属性为默认值不序列化
// Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,则返回的json是没有这个字段的
// Include.NON_NULL 属性为NULL 不序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 允许出现特殊字符和转义符
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
// 允许出现单引号
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); /**
* 将Long,BigInteger序列化的时候,转化为String
*/
// SimpleModule simpleModule = new SimpleModule();
//
// simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
// simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
// simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
//
// objectMapper.registerModule(simpleModule); // 将工具类中的 objectMapper 换为 Spring 中的 objectMapper
JacksonUtil.objectMapper = objectMapper;
return objectMapper;
}

这段代码是紧跟上一步,对Jackson2ObjectMapperBuilderbuilder出来的ObjectMapper做一些操作,设置一系列自己想要的属性。

代码中注释那一块也是做一个序列化转换,如果你的项目中用到了比较长的LONG类型数字,可能会导致JS拿不到完全的数字,因为java中的long类型要比JS的number类型长一点,这个时候你必须要转换成String给前台,它才能拿到正确的数字,如果你有需要可以打开这一段。

最后一句就是我们比较关键的了,把builder出来的ObjectMapper赋值给我们工具类中的ObjectMapper,这样的话它俩其实指向一个地址,也就是使用同一个对象进行序列化,所得出的结果当然就是相同的了。

后记

今天的从LocalDateTime序列化探讨全局一致性序列化就到这里了,希望对大家有所帮助。

本文的代码我也放在之前的SpringSecruity的demo中了,大家可以直接去里面搜索类名即可找到。

本文代码: 码云地址GitHub地址

日拱一卒无有尽,功不唐捐终入海。

你们的每个点赞收藏与评论都是对我知识输出的莫大肯定,如果有文中有什么错误或者疑点或者对我的指教都可以在评论区下方留言,一起讨论。

从LocalDateTime序列化探讨全局一致性序列化的更多相关文章

  1. java序列化和反序列化及序列化方式

    平时我们在Java内存中的对象,是无 法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即 存储对象中的状态 ...

  2. [.net 面向对象程序设计进阶] (13) 序列化(Serialization)(五) Json 序列化利器 Newtonsoft.Json 及 通用Json类

    [.net 面向对象程序设计进阶] (13) 序列化(Serialization)(五) Json 序列化利器 Newtonsoft.Json 及 通用Json类 本节导读: 关于JSON序列化,不能 ...

  3. 序列化战争:主流序列化框架Benchmark

    序列化战争:主流序列化框架Benchmark GitHub上有这样一个关于序列化的Benchmark,被好多文章引用.但这个项目考虑到完整性,代码有些复杂.为了个人学习,自己实现了个简单的Benchm ...

  4. 字定义JSON序列化支持datetime格式序列化

    字定义JSON序列化支持datetime格式序列化 由于json.dumps无法处理datetime日期,所以可以通过自定义处理器来做扩展,如: import json from datetime i ...

  5. .NET 二进制序列化器,SOAP序列化器,XML序列化器

    这里就不说JSON序列化了,只介绍三种:二进制序列化器,SOAP序列化器,XML序列化器 直接上代码: /// <summary> /// 二进制序列化器. /// 最节省流量,压缩程度最 ...

  6. Atitit php序列化 php的serialize序列化和json序列化

    Atitit php序列化 php的serialize序列化和json序列化 PHP 对不同类型的数据用不同的字母进行标示,Yahoo 开发网站提供的Using Serialized PHP with ...

  7. spring boot添加 LocalDateTime 等 java8 时间类序列化和反序列化的支持

    由于项目将原有的  Date类型的字段改造为 LocalDate,LocalDateTime,LocalTime 类型, 发现  spring  对项目的时间格式无法自动转换,故需手动配置下. 在sp ...

  8. [MVC_Json序列化]MVC之Json序列化循环引用

    在做MVC项目时,难免会遇到Json序列化循环引用的问题,大致错误如下 错误1:序列化类型为“...”的对象时检测到循环引用. 错误2:Self referencing loop detected f ...

  9. 关于Java序列化和Hadoop的序列化

    import java.io.DataInput; import java.io.DataOutput; import java.io.DataOutputStream; import java.io ...

随机推荐

  1. 黎活明8天快速掌握android视频教程--14_把文件存放在SDCard

    把文件保存在手机的内部存储空间中 1 首先必须在清单文件中添加权限 <?xml version="1.0" encoding="utf-8"?> & ...

  2. Python 简明教程 --- 8,Python 字符串函数

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 好代码本身就是最好的文档.当你需要添加一个注释时,你应该考虑如何修改代码才能不需要注释. -- St ...

  3. pikachu靶场-暴力破解(验证码、token)

    甲.基于表单的破解 较为简单,直接BurpSuite爆破. 乙.验证码绕过(on server) 打开题目,比第一题多了一个验证码验证,很多初学者遇到验证码就会感觉不知所措.其实这题也较为简单,首先正 ...

  4. .NET Core WEB API接口参数模型绑定

    .NET Core WEB API 模型绑定方式有以下表格中的几种: 特性 绑定源 [FromHeader] 请求标头 [FromQuery] 请求查询字符串参数 [FromForm] 请求正文中的表 ...

  5. Oracle 11g各种服务作用以及哪些需要开启

    Windwos server 2012 R2上成功安装Oracle 11g后共有7个服务,如果全局数据库名为orcl,则Oracle服务分别为 Oracle ORCL VSSWriter Servic ...

  6. 洛谷 P2648 赚钱

    这道题其实就是求最长路顺便再判断一下正环而已. 这种题肯定要用SPFA的啦,有又正边权(因为最长路所以正边就相当于负边),又是正环(同理,相当于负环),SPFA专治这种问题. 当一个点入队多次的时候, ...

  7. node解压压缩包以及压缩图片

    node解压压缩包以及压缩图片 首先保证电脑安装node环境,下载地址:http://nodejs.cn //可以打开一个dos窗口输入node -v进行确认是否安装成功 C:\ > node ...

  8. linux篇---根据端口号查看进程位置

    1)说明:Linux的所有进程都保存在/proc/目录下,保存形式为:/proc/进程号.进入到进程号目录后,里面有一个cwd链接文件即指向的进程的的目录. 2) 操作: A:根据端口号查进程 如:l ...

  9. 二叉搜索树的后序遍历序列(剑指offer-23)

    题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 题目解析 采用分治法的思想,找到根结点.左子树的序 ...

  10. Dot Net Core中间件内部运行机制

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { ...