专栏首页汤圆学Java日期格式化时注解@DateTimeFormat无效的问题分析
原创

日期格式化时注解@DateTimeFormat无效的问题分析

作者:汤圆

个人博客:javalover.cc

背景

有时候我们在写接口时,需要把前台传来的日期String类型转为Date类型

这时我们可能会用到@DateTimeFormat注解

在请求数据为非JSON格式时,这个注解是没有问题的,可用的;

但是当请求数据为JSON格式时,问题就出现了

  • 此时如果请求参数没有加@RequestBody注解,那么请求参数不会执行类型转换操作,数据都是默认为空(基本类型比如int = 0, 对象引用比如Date date= null)
  • 此时如果请求参数有加@RequestBody注解,那么请求参数会执行JSON类型转换操作,但是转换会提示异常

所以文章题目中所说的有时无效,指的就是上面这两种情况

目录

本文分三步走,如下所示,其中会穿插着介绍@DateTimeFormat、@RequestBody、@JsonFormat注解

分析

1. 基础代码:

AnnationApplication.java:主程序兼控制器

package com.jalon.annation;​import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;​@SpringBootApplication@RestControllerpublic class AnnationApplication {​  public static void main(String[] args) {    SpringApplication.run(AnnationApplication.class, args);  }​  @PostMapping("/personPost")  public Person personPost(Person person){    System.out.println(person);    return person;  }}

Person.java 实体类

package com.jalon.annation;​import com.fasterxml.jackson.annotation.JacksonAnnotation;import org.springframework.format.annotation.DateTimeFormat;import org.springframework.stereotype.Component;​import java.util.Date;​public class Person {​    private int age;​    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private Date birth;​    @Override    public String toString() {        return "Person{" +                "age=" + age +                ", birth=" + birth +                '}';    }    // 省略getter/setter}​

2. 案例分析:

这里我们用的是PostMan进行测试,请求示例如下

所有示例全程都有@DateTimeFormat注解

示例1:

  • 请求方式:Post请求
  • 数据格式:非JSON格式,比如form-data
  • 请求资源:personPost(Person person),无@RequestBody注解 具体请求内容和返回结果如下所示

可以看到,前台返回正常(数据无误),说明@DateTimeFormat有效,成功解析了日期字符串

这里返回的数据都是经过@ResponseBody处理过的,因为我们没有配置返回数据的日期格式化,所以这里返回的日期格式是默认的

@ResponseBody对应于@RequestBody;

  • 前者负责将Java对象序列号成JSON数据进行返回
  • 后者负责解析请求过来的JSON数据,解析成对应的Java对象

我们再来看下后台,打印如下:

Person{age=1, birth=Wed Jan 01 00:00:00 CST 2020}

可以看到,后台打印正常(数据无误,日期格式忽略,因为这里的date.toString用的Date的默认方法)

从上面的结果我们可以看到,@DateTimeFormat只是负责解析传来的日期字符串,转为对应的日期对象;

但是并不会修改原有的日期对象的格式(从前台返回和后台输出可以看到,日期格式不受@DateTimeFormat的影响)

示例2:

  • 请求方式:Post请求
  • 数据格式:JSON格式,比如application/json
  • 请求资源:personPost(Person person),无@RequestBody注解 具体请求内容和返回结果如下所示

可以看到,返回数据都为空(默认的初始值),说明数据都没有传过去,不止是date,连基本类型int都没过去

我们再来看下后台,打印如下

Person{age=0, birth=null} // 跟前台返回的数据一致

可以看到,后台解析到的数据也是空的,所以上面返回的当然是空的

原因就是默认的类型转换器是没有转化成JSON格式的对应转换类的,部分转换器如下所示,(core.convert.support包)

解决:所以这里对应的解决办法就是,自己创建一个JSON转换器

但是实际上这个已经有实现了,只是没有触发,如下所示的构建工具(http.converter.json包),就是用来配置相关的json序列化和反序列化的

现在我们可以通过@RequestBody注解来触发,它在接收到JSON格式的数据时,会自动调用对应的JSON转换器

下面的示例3就是这个例子

加了@RequestBody后,默认只接受application/json格式的数据,如果传入其他格式,会报415不支持的类型

示例3:

  • 请求方式:Post请求
  • 数据格式:JSON格式,比如application/json
  • 请求资源:personPost(@RequestBody Person person),有@RequestBody注解 具体请求内容和返回结果如下所示

可以看到,报错了,提示400,这种一般属于客户端错误(比如数据格式不正确,数据过大等)

我们再来看下后台,打印如下

2021-05-15 13:48:41.578  WARN 38426 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.Date` from String "2020-01-01 00:00:00": not a valid representation (error: Failed to parse Date value '2020-01-01 00:00:00': Cannot parse date "2020-01-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null)); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2020-01-01 00:00:00": not a valid representation (error: Failed to parse Date value '2020-01-01 00:00:00': Cannot parse date "2020-01-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null)) at [Source: (PushbackInputStream); line: 3, column: 14] (through reference chain: com.jalon.annation.Person["birth"])]​

这里我们提取关键的部分来看:

1. nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2020-01-01 00:00:00" 2. Cannot parse date "2020-01-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX'

首先这里跟示例2不同,这里起码做了尝试转换,只是没有找到对应的格式,所以转换失败了

可以看到,它并没有按照上面我们的@DateTimeFormat注解去解析,而是按照''yyyy-MM-dd'T'HH:mm:ss.SSSX"这个格式去解析

这里如果想投机的话,可以在前台直接传入''yyyy-MM-dd'T'HH:mm:ss.SSSX'格式的数据,如下:

但是这种办法对于前端很不友好(极其不好)

所以下面还是给出正常的解决办法

解决:所以这里的解决办法就是自己定义日期格式

  • 方案一:局部注解来解决,比如在date字段添加@JsonFormat()注解
// 这个注解用来解析JSON数据中的日期字符串,会序列化返回数据@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date birth;

局部的特点:灵活,但是配置繁琐,不统一(每个字段都要加)

  • 方案二:全局配置来解决,比如配置一个Jackson2ObjectMapperBuilderCustomizer,然后自定义日期反序列化格式
package com.jalon.annation;​import com.fasterxml.jackson.databind.deser.std.DateDeserializers;import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;​import java.text.SimpleDateFormat;import java.util.Date;​@Configurationpublic class MyDateConvertCustoms implements Jackson2ObjectMapperBuilderCustomizer {    @Override    public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {      // 覆盖默认的Date反序列化,第一个参数为需要反序列化的类,第二个为具体的序列化格式      jacksonObjectMapperBuilder.deserializerByType(                Date.class                ,new DateDeserializers.DateDeserializer(                        DateDeserializers.DateDeserializer.instance                        , new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")                        , null));    }}​

全局的特点:不灵活,但是直观清晰,配置统一

3. 结论分析:

主要根据请求的数据类型来对比

  • 请求非JSON数据,建议用@DateTimeFormat即可(比如get请求,当然get请求也可以请求JSON数据,只是不推荐)
  • 请求JSON数据,建议用@ReqeustBody来转换数据,然后搭配局部注解@JsonFormat或者全局配置来修改默认的日期解析格式(默认"yyyy-MM-dd'T'HH:mm:ss.SSSX")

总结

注解相关:

  1. @DateTimeFormat注解:适用于请求数据为非JSON数据,不会格式化返回数据
  2. @JsonFormat注解:适用于请求数据为JSON数据(尤其有日期数据时),且需在请求方法的参数前加@RequestBody`注解,会格式化返回数据
  3. @RequestBody注解:解析传来的JSON数据,转换成对应的Java对象
  4. @ResponseBody注解:转换Java对象为JSON数据,用来作为返回数据输出到前端

日期格式化相关:

  1. 请求非JSON数据,建议用@DateTimeFormat即可,此时不会格式化返回数据(比如get请求,当然get请求也可以请求JSON数据,只是不推荐)
  2. 请求JSON数据,建议用@ReqeustBody来转换数据,然后搭配局部注解@JsonFormat(会格式化返回数据)或者全局配置来修改默认的日期解析格式(默认"yyyy-MM-dd'T'HH:mm:ss.SSSX");全局配置也可以格式化返回数据,需配置builder.serializerByType
  3. 如果日期格式化出错,先看传来的数据是否为JSON数据(可以通过consumes来限制),然后再看有没有对于的注解或日期格式化全局配置

参考内容:

后记

学习之路漫漫,共勉之

写在最后:

愿你的意中人亦是中意你之人

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...

    Converter只完成了数据类型的转换,却不负责输入输出数据的格式化工作,日期时间、货币等虽都以字符串形式存在,却有不同的格式。

    YourBatman
  • Spring中使用Date参数

    在日常开发中,我们难免会遇到前端向后端传日期参数做查询操作, 在这篇文章中,我们将探索如何在请求和应用程序级别接受Spring REST请求中的Dat...

    叔牙
  • Layui解决table日期的格式化问题

    当然可以选择在后端格式化后再传回前端,显然我们不推荐你这样做,因为你很难确定,你格式化后的日期格式在所有地方都适用

    许喜朝
  • Spring官网阅读(十五)Spring中的格式化(Formatter)

    从上面可以看出,这个两个接口维护了两个功能相反的方法,分别完成对String类型数据的解析以及格式化。

    程序员DMZ
  • 【小家Spring】@DateTimeFormat和@NumberFormat以及@JsonFormat的使用

    数据格式化,从本质上讲属于数据转换的范畴。Spring就是基于数据转换框架植入“格式化”功能的。

    YourBatman
  • ABP入门系列(8)——Json格式化

    讲完了分页功能,这一节我们先不急着实现新的功能。来简要介绍下Abp中Json的用法。为什么要在这一节讲呢?当然是做铺垫啊,后面的系列文章会经常和Json这个东...

    圣杰
  • 格式化Mybatis返回的Date时间

    格式化Mybatis返回的Date时间

    Java架构师必看
  • spring mvc 时间日期转换(@DateTimeFormat 注解)

    两种用法示例: @GetMapping("/date") public String datest(@DateTimeFormat(iso=ISO.D...

    yawn
  • 【小家java】java8新特性之---全新的日期、时间API(JSR 310规范),附SpringMVC、Mybatis中使用JSR310的正确姿势

    Tiago Fernandez做了一个很有意思的投票,统计对Java API的不满意程度,最终Java Date/Time/Calendar API被评为最烂A...

    YourBatman
  • 什么在代码中要求我们使用LocalDateTime而不是Date?

    在项目开发过程中经常遇到时间处理,但是你真的用对了吗,理解阿里巴巴开发手册中禁用static修饰SimpleDateFormat吗?

    用户1516716
  • java 接口接受Date类型数据

    IT云清
  • 为什么建议你使用LocalDateTime而不是Date?

    calendar是共享变量,并且这个共享变量没有做线程安全控制。当多个线程同时使用相同的SimpleDateFormat对象【如用static修饰的Simple...

    Bug开发工程师
  • 为什么建议使用 LocalDateTime ,而不是 Date?

    使用 SimpleDateFormat对时间进行格式化,但 SimpleDateFormat是线程不安全的 SimpleDateFormat的 format方法...

    zhisheng
  • SSM的最后一剑-SpringMVC

        为了使Spring有可插入的MVC架构,SpringFrameWork在Spring基础上开发SpringMVC框架,从而在使用Spring进行WEB开...

    XiaoLin_Java
  • 为什么建议使用你 LocalDateTime ,而不是 Date?

    来源:juejin.im/post/5d7787625188252388753eae

    JAVA葵花宝典
  • 为什么建议使用你 LocalDateTime ,而不是 Date?

    来源:juejin.im/post/5d7787625188252388753eae

    用户1516716
  • 为什么建议使用你 LocalDateTime ,而不是 Date?

    多线程并发如何保证线程安全 - 避免线程之间共享一个SimpleDateFormat对象,每个线程使用时都创建一次SimpleDateFormat对象 => 创...

    芋道源码
  • EasyExcel,让excel导入导出更加简单

    在做excel导入导出的时候,发现项目中封装的工具类及其难用,于是去gitHub上找了一些相关的框架,最终选定了EasyExcel。之前早有听闻该框架,但是一直...

    全栈程序员站长
  • EasyExcel,让excel导入导出更加简单

    在做excel导入导出的时候,发现项目中封装的工具类及其难用,于是去gitHub上找了一些相关的框架,最终选定了EasyExcel。之前早有听闻该框架,但是一直...

    芋道源码

扫码关注云+社区

领取腾讯云代金券