千家信息网

SpringMVC实现日期格式属性自动转成时间戳

发表于:2025-02-06 作者:千家信息网编辑
千家信息网最后更新 2025年02月06日,这篇文章主要介绍"SpringMVC实现日期格式属性自动转成时间戳",在日常操作中,相信很多人在SpringMVC实现日期格式属性自动转成时间戳问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作
千家信息网最后更新 2025年02月06日SpringMVC实现日期格式属性自动转成时间戳

这篇文章主要介绍"SpringMVC实现日期格式属性自动转成时间戳",在日常操作中,相信很多人在SpringMVC实现日期格式属性自动转成时间戳问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"SpringMVC实现日期格式属性自动转成时间戳"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

背景介绍

SpringMVC搭建的微服务系统,后端数据库对时间类型的存储使用的是Long类型,而前端框架倾向于使用yyyy-MM-dd HH:mm:ss这种标准显示格式,前端JSON格式的请求报文与后台的接口交互都需要进行格式转换,这部分转换功能由后台实现。

使用时我们发现,前端定义的JSON请求,时间格式为yyyy-MM-dd HH:mm:ss,如果后台定义的POJO相应的属性为Long类型,可以自动转换为时间戳,对此非常好奇,框架是如何实现这一功能的?

框架选型、版本及主要功能
  1. spring boot 2.1.6.RELEASE

  2. spring cloud Greenwich.SR3

  3. alibaba fastjson 1.2.60

注意json框架使用的是fastjson

代码演示

为了方便演示,定义一个特别简单的POJO类:

public class DateReq {        private String dateFormat;        private Long timestamp;    // 省略getter/setter/toString方法}

再定义一个简单的Controller方法:

@RestControllerpublic 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)

发现了两处有价值的信息:

  1. 触发消息类型转换类是FastJsonHttpMessageConverter

  2. 真正完成类型映射是fastjson框架

有这个思路,阅读源码时可以把重点放在fastjson上,从JSON反序列化为POJO,Long类型字段处理,找到这段代码:

public  T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {        JSONLexer lexer = parser.lexer;        Long longObject;        try {            int token = lexer.token();            if (token == 2) {                long longValue = lexer.longValue();                lexer.nextToken(16);                longObject = longValue;            } else if (token == 3) {                BigDecimal number = lexer.decimalValue();                longObject = TypeUtils.longValue(number);                lexer.nextToken(16);            } else {                if (token == 12) {                    JSONObject jsonObject = new JSONObject(true);                    parser.parseObject(jsonObject);                    longObject = TypeUtils.castToLong(jsonObject);                } else {                    Object value = parser.parse();                    // 关注这一行,yyyy-MM-dd HH:mm:ss会执行这一行代码                    longObject = TypeUtils.castToLong(value);                }                if (longObject == null) {                    return null;                }            }        } catch (Exception var9) {            throw new JSONException("parseLong error, field : " + fieldName, var9);        }        return clazz == AtomicLong.class ? new AtomicLong(longObject) : longObject;    }

重点关注longObject = TypeUtils.castToLong(value);yyyy-MM-dd HH:mm:ss格式的数据会执行这一行代码,跟进去查看源码:

public static Long castToLong(Object value) {        if (value == null) {            return null;        } else if (value instanceof BigDecimal) {            return longValue((BigDecimal)value);        } else if (value instanceof Number) {            return ((Number)value).longValue();        } else {            if (value instanceof String) {                String strVal = (String)value;                if (strVal.length() == 0 || "null".equals(strVal) || "NULL".equals(strVal)) {                    return null;                }                if (strVal.indexOf(44) != 0) {                    strVal = strVal.replaceAll(",", "");                }                try {                    return Long.parseLong(strVal);                } catch (NumberFormatException var4) {                    // 在异常里做最后的挣扎,今天的案例是执行到这里的                    JSONScanner dateParser = new JSONScanner(strVal);                    Calendar calendar = null;                    if (dateParser.scanISO8601DateIfMatch(false)) {                        calendar = dateParser.getCalendar();                    }                    dateParser.close();                    if (calendar != null) {                        return calendar.getTimeInMillis();                    }                }            }            if (value instanceof Map) {                Map map = (Map)value;                if (map.size() == 2 && map.containsKey("andIncrement") && map.containsKey("andDecrement")) {                    Iterator iter = map.values().iterator();                    iter.next();                    Object value2 = iter.next();                    return castToLong(value2);                }            }            throw new JSONException("can not cast to long, value : " + value);        }    }

可以看到在castToLong方法里,对假想的数据类型做各种假设处理,很不幸的是我们试验的数据格式,是在NumberFormatException异常里完成的最后挣扎,使用JSONScanner类接收的请求数据。

可以看到在这里通过调用dateParser.scanISO8601DateIfMatch对数据进行解析,得到calendar对象实例,最终通过calendar获取时间戳,scanISO8601DateIfMatch方法逻辑很复杂,总共有450多行,这里截取了其中一部分展现一下:

private boolean scanISO8601DateIfMatch(boolean strict, int rest) {        if (rest < 8) {            return false;        }        char c0 = charAt(bp);        char c1 = charAt(bp + 1);        char c2 = charAt(bp + 2);        char c3 = charAt(bp + 3);        char c4 = charAt(bp + 4);        char c5 = charAt(bp + 5);        char c6 = charAt(bp + 6);        char c7 = charAt(bp + 7);        if ((!strict) && rest > 13) {            char c_r0 = charAt(bp + rest - 1);            char c_r1 = charAt(bp + rest - 2);        }        char c10;               if (rest < 9) {            return false;        }        char c8 = charAt(bp + 8);        char c9 = charAt(bp + 9);        int date_len = 10;        char y0, y1, y2, y3, M0, M1, d0, d1;        if ((c4 == '-' && c7 == '-') // cn                ||  (c4 == '/' && c7 == '/') // tw yyyy/mm/dd        ) {            y0 = c0;            y1 = c1;            y2 = c2;            y3 = c3;            M0 = c5;            M1 = c6;            d0 = c8;            d1 = c9;        } else if ((c4 == '-' && c6 == '-') // cn yyyy-m-dd        ) {            y0 = c0;            y1 = c1;            y2 = c2;            y3 = c3;            M0 = '0';            M1 = c5;            if (c8 == ' ') {                d0 = '0';                d1 = c7;                date_len = 8;            } else {                d0 = c7;                d1 = c8;                date_len = 9;            }        } else if ((c2 == '.' && c5 == '.') // de dd.mm.yyyy                || (c2 == '-' && c5 == '-') // in dd-mm-yyyy        ) {            d0 = c0;            d1 = c1;            M0 = c3;            M1 = c4;            y0 = c6;            y1 = c7;            y2 = c8;            y3 = c9;        } else if (c8 == 'T') {            y0 = c0;            y1 = c1;            y2 = c2;            y3 = c3;            M0 = c4;            M1 = c5;            d0 = c6;            d1 = c7;            date_len = 8;        } else {            if (c4 == '年' || c4 == '년') {                y0 = c0;                y1 = c1;                y2 = c2;                y3 = c3;                if (c7 == '月' || c7 == '월') {                    M0 = c5;                    M1 = c6;                    if (c9 == '日' || c9 == '일') {                        d0 = '0';                        d1 = c8;                    } else if (charAt(bp + 10) == '日' || charAt(bp + 10) == '일'){                        d0 = c8;                        d1 = c9;                        date_len = 11;                    } else {                        return false;                    }                } else if (c6 == '月' || c6 == '월') {                    M0 = '0';                    M1 = c5;                    if (c8 == '日' || c8 == '일') {                        d0 = '0';                        d1 = c7;                    } else if (c9 == '日' || c9 == '일'){                        d0 = c7;                        d1 = c8;                    } else {                        return false;                    }                } else {                    return false;                }            } else {                return false;            }        }        if (!checkDate(y0, y1, y2, y3, M0, M1, d0, d1)) {            return false;        }        setCalendar(y0, y1, y2, y3, M0, M1, d0, d1);        char t = charAt(bp + date_len);               if (charAt(bp + date_len + 3) != ':') {            return false;        }        if (charAt(bp + date_len + 6) != ':') {            return false;        }        char h0 = charAt(bp + date_len + 1);        char h2 = charAt(bp + date_len + 2);        char m0 = charAt(bp + date_len + 4);        char m1 = charAt(bp + date_len + 5);        char s0 = charAt(bp + date_len + 7);        char s1 = charAt(bp + date_len + 8);        if (!checkTime(h0, h2, m0, m1, s0, s1)) {            return false;        }        setTime(h0, h2, m0, m1, s0, s1);        char dot = charAt(bp + date_len + 9);        int millisLen = -1; // 有可能没有毫秒区域,没有毫秒区域的时候下一个字符位置有可能是'Z'、'+'、'-'        int millis = 0;               calendar.set(Calendar.MILLISECOND, millis);        int timzeZoneLength = 0;        char timeZoneFlag = charAt(bp + date_len + 10 + millisLen);        if (timeZoneFlag == ' ') {            millisLen++;            timeZoneFlag = charAt(bp + date_len + 10 + millisLen);        }        char end = charAt(bp + (date_len + 10 + millisLen + timzeZoneLength));        if (end != EOI && end != '"') {            return false;        }        ch = charAt(bp += (date_len + 10 + millisLen + timzeZoneLength));        token = JSONToken.LITERAL_ISO8601_DATE;        return true;    }

支持的格式还是挺多,不过基本上符合国内的日期使用习惯,像2020-08-08和2020/08/08,甚至2020年08月08日都行,解析的思路是按位截取判断,然后作为Calendar的参数,上述节选的代码有删节,有兴趣可以查看原代码。

到此,关于"SpringMVC实现日期格式属性自动转成时间戳"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

格式 类型 时间 代码 数据 方法 框架 属性 日期 学习 一行 前端 功能 后台 字段 报文 源码 好奇 区域 参数 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 怎么通过服务器访问谷歌 网易梦幻西游哪个服务器不卡 计算机网络技术鉴定表感悟 都中国网络安全 农行软件开发中心社招待遇 夏门一品威客软件开发公司 浙江网络安全局 研究网络安全实验方法 网络安全法表态发言 上海物联网智慧医院软件开发 平谷区数据网络技术售后服务 天问小说软件开发 软件开发竞争策略与发展规划 云帮手服务器安全管理 莫纳什网络安全有实战吗 贵州便民平台软件开发哪儿好 外服吃鸡为什么会服务器繁忙 我国成立中央网络技术 北京新一代网络技术产品介绍 软件开发容器化开发 绝地求生炸服务器 如何提交数据库 长沙裕邦软件开发有限公司地址 数据库系统答案 北邮网络安全是否是a类学科 Java代码怎么执行服务器命令 长沙呐百网络技术有限公司 电话打通暂时无法连接服务器 学游戏软件开发上什么学校 安徽潮流软件开发价格标准
0