近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。
本项目基于Java21和SpringBoot3开发,序列化工具使用的是默认的Jackson,使用Spring Data Redis操作Redis缓存。
在定义实体类过程中,日期时间类型的属性我使用了java.time
包下的LocalDate
和LocalDateTime
类,而没有使用java.util
包下的Date
类。
但在使用过程中遇到了一些问题,于是在此记录下来与诸位分享。
LocalDateTime和Date是Java中表示日期和时间的两种不同的类,它们有一些区别和特点。
由于LocalDateTime是Java 8及以上版本的新类型,并提供了更多的功能和灵活性,推荐在新的项目中使用LocalDateTime来处理日期和时间。
对于旧版Java项目,仍然需要使用Date类,但在多线程环境下需要注意其线程安全性。
如果需要在LocalDateTime和Date之间进行转换,可以使用相应的方法进行转换,例如通过LocalDateTime的atZone()方法和Date的toInstant()方法进行转换。
在使用RedisTemplate向Redis中插入数据时,遇到了如下报错:
2024-01-11T21:33:25.233+08:00 ERROR 13212 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
org.springframework.data.redis.serializer.SerializationException: Could not write JSON: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: java.util.ArrayList[0]->com.fast.alden.data.model.SysApiResource["createdTime"])
at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.serialize(Jackson2JsonRedisSerializer.java:157) ~[spring-data-redis-3.2.0.jar:3.2.0]
at org.springframework.data.redis.core.AbstractOperations.rawValue(AbstractOperations.java:128) ~[spring-data-redis-3.2.0.jar:3.2.0]
at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:236) ~[spring-data-redis-3.2.0.jar:3.2.0]
在使用Redis缓存含有LocalDateTime类型变量的实体类时会产生序列化问题,因为Jackson库在默认情况下不支持Java8的LocalDateTime类型的序列化和反序列化。
错误堆栈中也给出了解决方案,添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310
依赖,但光添加依赖是不够的,还我们需要自定义序列化和反序列化的行为。
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.0</version>
</dependency>
在定义RedisSerializer Bean的代码中自定义ObjectMapper对象处理时间属性时的序列化和反序列化行为,LocalDate
、LocalDateTime
、LocalTime
的序列化和反序列化都要自定义,还要禁用将日期序列化为时间戳。
@Configuration
public class RedisConfig {
@Bean
public RedisSerializer<Object> redisSerializer() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 必须设置,否则无法将JSON转化为对象,会转化成Map类型
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// 自定义ObjectMapper的时间处理模块
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
objectMapper.registerModule(javaTimeModule);
// 禁用将日期序列化为时间戳的行为
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
//创建JSON序列化器
return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
}
}
在application.yml中设置了全局的日期类型的序列化和反序列化格式,在对应字段上也并没有使用@JsonFormat进行特殊设置,但是LocalDateTime类型的属性返回给前端时并没有生效,返回的仍是LocalDateTime默认的ISO标准时间格式的字符串。
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: always
mvc:
format:
date-time: yyyy-MM-dd HH:mm:ss
date: dd/MM/yyyy
自定义Jackson配置,代码如下:
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder ->
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss")
// long类型转string, 前端处理Long类型,数值过大会丢失精度
.serializerByType(Long.class, ToStringSerializer.instance)
.serializerByType(Long.TYPE, ToStringSerializer.instance)
.serializationInclusion(JsonInclude.Include.NON_NULL)
//指定反序列化类型,也可以使用@JsonFormat(pattern = "yyyy-MM-dd")替代。主要是mvc接收日期时使用
.deserializerByType(LocalTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")))
.deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
// 日期序列化,主要返回数据时使用
.serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")))
.serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
}
在使用java.time API的过程中,除了会遇到前文所说的序列化问题之外,可能还会遇到以下问题:
我也会及时的更新后续实践中所遇到的问题,希望与诸位看官一起进步。
如有错误,还望批评指正。