现象
使用 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-format 和 spring.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 类型的字段创建一个新的序列化器。