0 序言
今日工作中遇到的一个bug。各位看官且听我娓娓道来。
1 问题描述
请求接口时,service
层返回到controller
层的数据结构为List<Map<Strig, Object>>
,而Map
中存在一个key
=date
,value type=java.time.LocalDate
的Entry
,且日志报如下错误:
InvalidDefinitionException: Java 8 date/time type `java.time.LocalDate` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: cn.xx.bd.dataservice.common.dto.CommonResponse["data"]->cn.xx.bd.dataservice.common.dto.page.v2.PageResponse["records"]->java.util.LinkedList[0]->java.util.HashMap["date"])”
2 问题分析
jackson
默认不支持java8 LocalDate/LocalDateTime
的序列化和反序列化,那控制台也显示了解决的办法(引入依赖com.fasterxml.jackson.datatype:jackson-datatype-jsr310
,并启用对Map
中"date"的entry的处理),只不过并不全。
因为
spring-mvc
/spring-boot
是使用jackson
作为json
序列化和反序列化工具的,故只需配置jackson
即可
3 解决方法
解决方法1 : 类型转换(LocalDate/LocalDateTime
--> Date
)
将实体类中的
LocalDate
/LocalDateTime
转为Date
类型
解决方法2
Step1 引入依赖(com.fasterxml.jackson.datatype:jackson-datatype-jsr310)
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.11.4</version>
</dependency>
或spring-boot
项目中直接引用:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
Step2 策略1:配置Jackson的序列化/反序列化策略 # 为特定Class Bean的字段指定Jackson的序列化策略
通过注解指定
@JsonFormat
/@DateTimeFormat
/@JsonDeserialize
/@JsonSerialize
@TableField("update_time")
@ApiModelProperty("更新时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //此注解用来接收字符串类型的参数封装成LocalDateTime类型
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8", shape = JsonFormat.Shape.STRING) //此注解将date类型数据转成字符串响应出去
@JsonDeserialize(using = LocalDateTimeDeserializer.class) // 反序列化
@JsonSerialize(using = LocalDateTimeSerializer.class) // 序列化
private LocalDateTime updateTime;
@TableField("create_time")
@ApiModelProperty("添加时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") //此注解用来接收字符串类型的参数封装成LocalDateTime类型
@JsonFormat(pattern = "yyyy-MM-dd HH:mm",timezone = "GMT+8", shape = JsonFormat.Shape.STRING) //此注解将date类型数据转成字符串响应出去
@JsonDeserialize(using = LocalDateTimeDeserializer.class) // 反序列化
@JsonSerialize(using = LocalDateTimeSerializer.class) // 序列化
private LocalDateTime createTime;
Step2 策略2:配置Jackson的序列化/反序列化策略 # 全局配置(MvcConfiguration)
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
messageConverter.setObjectMapper(objectMapper);
converters.add(0, messageConverter);
}
}
或:
- Step2.1 JacksonConfiguration
import cn.xx.bd.dataservice.biz.common.constants.Constants;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.context.annotation.Bean;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class JacksonConfiguration {
@Bean
public ObjectMapper objectMapper(){
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_DATE_FORMAT)));
javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_TIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_DATE_FORMAT)));
javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_TIME_FORMAT)));
objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule());
return objectMapper;
}
}
- Step2.2 Constants
public class Constants {
public static class DateTime {
/** 默认日期时间格式 */
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/** 默认日期格式 */
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
/** 默认时间格式 */
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
}
}
然后,只需要在实体类中对应的时间类型上使用
@DateTimeFormat
和@JsonFormat
即可。
解决方法3 以FastJson框架替换Jackson ObjectMapper
用阿里的FastJson替换ObjectMapper
X 参考文献
- spring boot添加 LocalDateTime 等 java8 时间类序列化和反序列化的支持 - 博客园 【推荐】
- Java 8 date/time type
java.time.LocalDateTime
not supported by default:日期序列化问题 - CSDN 【推荐】 - Java 8 中 Jackson 序列化 LocalDateTime 的问题 - Zhihu