Redis中序列化Java日期时区问题

现象

使用 Redis pub/sub 发布 Java 对象,订阅端服务接收到该对象后发现日期字段都差了 8 个小时。

Redis 的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Configuration
@EnableCaching
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);

ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

JavaTimeModule javaTimeModule = new JavaTimeModule();
om.registerModule(javaTimeModule);

Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(om, Object.class);
StringRedisSerializer stringSerializer = new StringRedisSerializer(StandardCharsets.UTF_8);

redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(serializer);

redisTemplate.afterPropertiesSet();
return redisTemplate;
}

@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
return redisMessageListenerContainer;
}
}

原因

发布对象的时候没有先转成 Json,即时是在 application.yml 文件中配置了 spring.jackson.date-formatspring.jackson.time-zone 也无法解决时区的问题。
只能修改 RedisTemplate 序列化行为。

具体就是给 JavaTimeModule 指定自定义的序列化器,对日期类型,按照自定义逻辑进行序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

JavaTimeModule javaTimeModule = new JavaTimeModule();
// 指定自定义序列化器
JsonSerializer customLocalDateTimeSerializer = new CustomLocalDateTimeSerializer("yyyy-MM-dd HH:mm:ss", "Asia/Shanghai");
JsonSerializer customTimestampSerializer = new CustomTimestampSerializer("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, customLocalDateTimeSerializer);
javaTimeModule.addSerializer(Timestamp.class, customTimestampSerializer);

om.registerModule(javaTimeModule);
om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(om, Object.class);

CustomLocalDateTimeSerializer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CustomLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
private final DateTimeFormatter dateTimeFormatter;
private final ZoneId zoneId;

public CustomLocalDateTimeSerializer(String pattern, String zoneId) {
this.dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
this.zoneId = ZoneId.of(zoneId);
}

@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (localDateTime == null) {
jsonGenerator.writeNull();
} else {
String formatted = localDateTime.atZone(zoneId).format(dateTimeFormatter);
jsonGenerator.writeString(formatted);
}
}
}

CustomTimestampSerializer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CustomTimestampSerializer extends JsonSerializer<Timestamp> {
private final SimpleDateFormat dateTimeFormatter;

public CustomTimestampSerializer(String pattern) {
this.dateTimeFormatter = new SimpleDateFormat(pattern);
}

@Override
public void serialize(Timestamp timestamp, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (timestamp == null) {
jsonGenerator.writeNull();
} else {
String formatted = dateTimeFormatter.format(timestamp);
jsonGenerator.writeString(formatted);
}
}
}

这里有个坑,被序列化的对象中的创建日期、更新日期字段是通过 MyBatis 中数据库中查出来的,查询的时候没有指定类型,默认类型是 java.sql.Timestamp,而非 java.util.Date,因此还需要额外对 java.sql.Timestamp 类型的字段创建一个新的序列化器。