背景介绍
SpringMVC搭建的微服务系统,后端数据库对时间类型的存储使用的是Long类型,而前端框架倾向于使用yyyy-MM-dd HH:mm:ss这种标准显示格式,前端JSON格式的请求报文与后台的接口交互都需要进行格式转换,这部分转换功能由后台实现。
使用时我们发现,前端定义的JSON请求,时间格式为yyyy-MM-dd HH:mm:ss,如果后台定义的POJO相应的属性为Long类型,可以自动转换为时间戳,对此非常好奇,框架是如何实现这一功能的?
框架选型、版本及主要功能
- spring boot 2.1.6.RELEASE
- spring cloud Greenwich.SR3
- alibaba fastjson 1.2.60
注意json框架使用的是fastjson
代码演示
为了方便演示,定义一个特别简单的POJO类:
public class DateReq {
private String dateFormat;
private Long timestamp;
// 省略getter/setter/toString方法
}
再定义一个简单的Controller方法:
@RestController
public class DemoController {
@PostMapping(value = "/json/demo/info")
public ApiResponse<?> dateJson(@RequestBody DateReq request) {
System.out.println(request);
}
}
请求报文如下:
{
"dateFormat": "2020-08-07 18:50:00",
"timestamp": "2020-08-07 18:50:00"
}
响应的结果:DateReq{dateFormat='2020-08-07 18:50:00', timestamp=1596797400000}
从结果可以发现,dateFormat字段我们定义的是String类型,timestamp定义的是Long类型,请求报文两个字段使用相同的值,但是到了Controller方法里,timestamp自动变成Long类型的时间戳了,并且是按东8区转换的。
在这里我们可以得到一个使用经验:POJO的时间格式是可以自动转换成Long类型时间戳的,默认时区取操作系统的时区,或者通过jvm参数-Duser.timezone=GMT+08
设置。
源码阅读
既然看到了自动转换的效果,非常好奇框架是怎么实现的,我们通过断点查找堆栈:
deserialze:79, LongCodec (com.alibaba.fastjson.serializer)
parseField:85, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:1224, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:850, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseRest:1538, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:-1, FastjsonASMDeserializer_3_DateReq (com.alibaba.fastjson.parser.deserializer)
deserialze:284, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:692, DefaultJSONParser (com.alibaba.fastjson.parser)
parseObject:383, JSON (com.alibaba.fastjson)
parseObject:448, JSON (com.alibaba.fastjson)
parseObject:556, JSON (com.alibaba.fastjson)
readType:263, FastJsonHttpMessageConverter (com.alibaba.fastjson.support.spring)
read:237, FastJsonHttpMessageConverter (com.alibaba.fastjson.support.spring)
readWithMessageConverters:204, AbstractMessageConverterMethodArgumentResolver (org.springframework.web.servlet.mvc.method.annotation)
readWithMessageConverters:157, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
resolveArgument:130, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
resolveArgument:124, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)
发现了两处有价值的信息:
- 触发消息类型转换类是FastJsonHttpMessageConverter
- 真正完成类型映射是fastjson框架
有这个思路,阅读源码时可以把重点放在fastjson上,从JSON反序列化为POJO,Long类型字段处理,找到这段代码:
public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {
JSONLexer lexer = parser.lexer;
Long longObject;
try {
int token = lexer.token();
if (token == 2) {
long longValue =