使用OpenFeign时LocalDateTime反序列化失败的解决方法

前言

​ 我在使用 OpenFeign 进行远程调用时,发现我的调用端报错 Cannot deserialize value of type java.time.LocalDateTime from String "2022-09-13T11:34:36" ,下面是错误排查与修改的过程。

异常描述

​ OpenFeign使用Get远程调用服务时,返回的dto如果包含了 LocalDateTime 的话,会导致远程调用失败,报Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2022-09-13T11:34:36' could not be parsed at index 10 错误。

image-20230816072604405

异常产生的原因

​ 查阅资料后发现,SpringBoot中默认使用Jackson做Json序列化和反序列化,服务器使用openFeign接收数据时将日期字符串转成LocalDateTime时,因为jsr310协议的默认的反序列化器格式是解析 2022-09-13T11:34:36 这样格式的字符串的。

​ 查看源码可以知道,默认解析String字符串时,会以 DEFAULT_FORMATTER 的格式进行解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (_formatter == DEFAULT_FORMATTER) {
// ... only allow iff lenient mode enabled since
// JavaScript by default includes time and zone in JSON serialized Dates (UTC/ISO instant format).
// And if so, do NOT use zoned date parsing as that can easily produce
// incorrect answer.
if (string.length() > 10 && string.charAt(10) == 'T') {
if (string.endsWith("Z")) {
if (isLenient()) {
return LocalDateTime.parse(string.substring(0, string.length()-1),
_formatter);
}
JavaType t = getValueType(ctxt);
return (LocalDateTime) ctxt.handleWeirdStringValue(t.getRawClass(),
string,
"Should not contain offset when 'strict' mode set for property or type (enable 'lenient' handling to allow)"
);
}
}
}
return LocalDateTime.parse(string, _formatter);

DEFAULT_FORMATTER 默认使用的日期格式为下图的 ISO_LOCAL_DATE_TIME 我们可以看到是带字符 T 的。

image-20230816075102644

​ 但是在根据根据2.12版本的[modules-base#94]的更改,我们的String时间字符串要解析成LocalDateTime格式时,不仅要第十个字符为T,还需要以字符Z结尾(这里我也不知道为什么这样设计)。

image-20230816075705607

​ 继续追踪源码,发现因为我们第十个字符是T,导致反序列化解析失败。

image-20230816081428412

上面代码的注释意思:

解析指定的文本。

这将解析为一个TemporalAccessor,确保文本被完全解析。

参数:

  • Text -要解析的文本,不是null

  • position -要解析的位置,更新为解析的长度和任何错误的索引,如果解析整个字符串,则为null

返回:

解析的结果,不是null

抛出:

  • DateTimeParseException:解析失败

  • DateTimeException:在解析日期或时间时发生错误

  • IndexOutOfBoundsException:位置无效

解决办法

在我们dto中,将我们的日期格式添加上以下代码

1
@JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss")

需要注意的是,一定要加 shape = JsonFormat.Shape.STRING 这个可以帮我们把T去掉。

加了之后我们在看debug看一下效果:

image-20230816082128025

可以发现这里的T不见了,并且解析结果也是成功的,至此问题解决。

需要注意的是:如果你的实体类模块是与service分开的,你需要重新构建工程。比如说,你的实体类是放在xxx-domain模块下的话,你修改之后重启xxx-service模块是不会生效的,因为没有重构项目生成新的target