在使用Redis时,常用的值序列化器为GenericJackson2JsonRedisSerializer,但是该序列化器默认不支持Java8的日期相关类(java.time.*);
测试代码如下:
public class Demo {
public static void main(String[] args) {
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
OffsetDateTime now = OffsetDateTime.now();
System.out.println(now);
byte[] serialize = serializer.serialize(now);
OffsetDateTime deserialize = serializer.deserialize(serialize, OffsetDateTime.class);
System.out.println(deserialize);
}
}
代码运行过程中,会发生序列化错误,错误简要信息如下:
Exception in thread "main" org.springframework.data.redis.serializer.SerializationException: Could not write JSON: Java 8 date/time type `java.time.OffsetDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling;问题分析
根据提示信息,加入jackson-datatype-jsr310依赖,如下:
com.fasterxml.jackson.datatype jackson-datatype-jsr310
重新运行后,报错依然;
除了需要引入jackson-datatype-jsr310依赖外,还需要增加JavaTimeModule,代码如下:
public class Demo {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper);
OffsetDateTime now = OffsetDateTime.now();
System.out.println(now);
byte[] serialize = serializer.serialize(now);
OffsetDateTime deserialize = serializer.deserialize(serialize, OffsetDateTime.class);
System.out.println(deserialize);
}
}
重新运行后,序列化报错问题解决了,但是发现,两次打印信息不一致,如下:
2022-04-20T20:57:09.910+08:00 2022-04-20T12:57:09.910Z
可以看出,经过序列化和反序列化后,时区信息丢失;
那么,此处就需要分析两个问题:
- 日期类如何序列化?
- 时区信息如何丢失?
代码中,使用JavaTimeModule后,序列化器就支持日期类的序列化,那么直接分析JavaTimeModule做了啥,核心代码如下:
public JavaTimeModule()
{
super(PackageVersion.VERSION);
// First deserializers
// // Instant variants:
addDeserializer(Instant.class, InstantDeserializer.INSTANT);
addDeserializer(OffsetDateTime.class, InstantDeserializer.OFFSET_DATE_TIME);
addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME);
// // Other deserializers
addDeserializer(Duration.class, DurationDeserializer.INSTANCE);
addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE);
addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE);
addDeserializer(MonthDay.class, MonthDayDeserializer.INSTANCE);
addDeserializer(OffsetTime.class, OffsetTimeDeserializer.INSTANCE);
addDeserializer(Period.class, JSR310StringParsableDeserializer.PERIOD);
addDeserializer(Year.class, YearDeserializer.INSTANCE);
addDeserializer(YearMonth.class, YearMonthDeserializer.INSTANCE);
addDeserializer(ZoneId.class, JSR310StringParsableDeserializer.ZONE_ID);
addDeserializer(ZoneOffset.class, JSR310StringParsableDeserializer.ZONE_OFFSET);
// then serializers:
addSerializer(Duration.class, DurationSerializer.INSTANCE);
addSerializer(Instant.class, InstantSerializer.INSTANCE);
addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE);
addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE);
addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE);
addSerializer(MonthDay.class, MonthDaySerializer.INSTANCE);
addSerializer(OffsetDateTime.class, OffsetDateTimeSerializer.INSTANCE);
addSerializer(OffsetTime.class, OffsetTimeSerializer.INSTANCE);
addSerializer(Period.class, new ToStringSerializer(Period.class));
addSerializer(Year.class, YearSerializer.INSTANCE);
addSerializer(YearMonth.class, YearMonthSerializer.INSTANCE);
addSerializer(ZonedDateTime.class, ZonedDateTimeSerializer.INSTANCE);
addSerializer(ZoneId.class, new ZoneIdSerializer());
addSerializer(ZoneOffset.class, new ToStringSerializer(ZoneOffset.class));
// key serializers
addKeySerializer(ZonedDateTime.class, ZonedDateTimeKeySerializer.INSTANCE);
// key deserializers
addKeyDeserializer(Duration.class, DurationKeyDeserializer.INSTANCE);
addKeyDeserializer(Instant.class, InstantKeyDeserializer.INSTANCE);
addKeyDeserializer(LocalDateTime.class, LocalDateTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(LocalDate.class, LocalDateKeyDeserializer.INSTANCE);
addKeyDeserializer(LocalTime.class, LocalTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(MonthDay.class, MonthDayKeyDeserializer.INSTANCE);
addKeyDeserializer(OffsetDateTime.class, OffsetDateTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(OffsetTime.class, OffsetTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(Period.class, PeriodKeyDeserializer.INSTANCE);
addKeyDeserializer(Year.class, YearKeyDeserializer.INSTANCE);
addKeyDeserializer(YearMonth.class, YearMonthKeyDeserializer.INSTANCE);
addKeyDeserializer(ZonedDateTime.class, ZonedDateTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(ZoneId.class, ZoneIdKeyDeserializer.INSTANCE);
addKeyDeserializer(ZoneOffset.class, ZoneOffsetKeyDeserializer.INSTANCE);
}
好家伙,简单直接地引入了Java8的日期相关类(java.time.*)的序列化器、反序列化器;其中为OffsetDateTime引入了OffsetDateTimeSerializer和OffsetDateTimeKeyDeserializer;
解析来就分析serializer.serialize(now)如何找到OffsetDateTimeSerializer的?
GenericJackson2JsonRedisSerializer.serialize代码如下:
public byte[] serialize(@Nullable Object source) throws SerializationException {
if (source == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
// 使用内部的ObjectMapper实例完成序列化
return mapper.writeValueAsBytes(source);
} catch (JsonProcessingException e) {
throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
}
}
继续跟踪,会进入SerializerProvider.findTypedValueSerializer方法,代码如下:
public JsonSerializer
继续跟踪,来到创建序列化器的方法BeanSerializerFactory.createSerializer,代码如下:
public JsonSerializercreateSerializer(SerializerProvider prov, JavaType origType) throws JsonMappingException { // 获取序列化配置信息 final SerializationConfig config = prov.getConfig(); BeanDescription beanDesc = config.introspect(origType); // 尝试根据类上的注解找到序列化器 JsonSerializer> ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo()); if (ser != null) { // 找到则优先使用 return (JsonSerializer ) ser; } boolean staticTyping; // Next: we may have annotations that further indicate actual type to use (a super type) // 如果存在AnnotationIntrospector,则使用其找到相关注解标注的序列化类型 // 默认为JacksonAnnotationIntrospector,注解为JsonSerialize final AnnotationIntrospector intr = config.getAnnotationIntrospector(); JavaType type; if (intr == null) { type = origType; } else { try { type = intr.refineSerializationType(config, beanDesc.getClassInfo(), origType); } catch (JsonMappingException e) { return prov.reportBadTypeDefinition(beanDesc, e.getMessage()); } } if (type == origType) { staticTyping = false; } else { staticTyping = true; if (!type.hasRawClass(origType.getRawClass())) { beanDesc = config.introspect(type); } } // 是否存在Convertor Converter conv = beanDesc.findSerializationConverter(); if (conv == null) { // 不存在,则直接创建并返回 return (JsonSerializer ) _createSerializer2(prov, type, beanDesc, staticTyping); } JavaType delegateType = conv.getOutputType(prov.getTypeFactory()); if (!delegateType.hasRawClass(type.getRawClass())) { beanDesc = config.introspect(delegateType); ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo()); } if (ser == null && !delegateType.isJavaLangObject()) { ser = _createSerializer2(prov, delegateType, beanDesc, true); } return new StdDelegatingSerializer(conv, delegateType, ser); }
最终来到SimpleSerializers.findSerializer方法,代码如下:
public JsonSerializer> findSerializer(SerializationConfig config,
JavaType type, BeanDescription beanDesc)
{
Class> cls = type.getRawClass();
ClassKey key = new ClassKey(cls);
JsonSerializer> ser = null;
// 是否是接口
if (cls.isInterface()) {
if (_interfaceMappings != null) {
ser = _interfaceMappings.get(key);
if (ser != null) {
return ser;
}
}
} else {
if (_classMappings != null) {
// 从_classMappings中查找对应的序列化器
ser = _classMappings.get(key);
if (ser != null) {
return ser;
}
// 代码省略
...
}
}
// 代码省略
...
return null;
}
在该方法中,根据java.time.OffsetDateTime找到OffsetDateTimeSerializer,那么这个核心的_classMappings是如何赋值的呢?
其赋值流程如下:
- JavaTimeModule调用父类SimpleModule的addSerializer方法增加了OffsetDateTime的序列化器 OffsetDateTimeSerializer.INSTANCE;
- addSerializer使用内部SimpleSerializers实例缓存了类型和序列化器的映射关系;
- 这样在SimpleSerializers中,就可以根据类型找到对应的序列化器了;
接下来就是序列化器序列化的实现了;
序列化序列化的入口为DefaultSerializerProvider._serialize,此时序列化器为OffsetDateTimeSerializer.INSTANCE,OffsetDateTime类序列化真正的实现在InstantSerializerBase.serialize方法,代码如下:
public void serialize(T value, JsonGenerator generator, SerializerProvider provider) throws IOException
{
if (useTimestamp(provider)) {
if (useNanoseconds(provider)) {
// 默认使用nanosecond timestamps
generator.writeNumber(DecimalUtils.toBigDecimal(
getEpochSeconds.applyAsLong(value), getNanoseconds.applyAsInt(value)
));
return;
}
generator.writeNumber(getEpochMillis.applyAsLong(value));
return;
}
generator.writeString(formatValue(value, provider));
}
getEpochSeconds和getNanoseconds的赋值见OffsetDateTimeSerializer.INSTANCE,代码如下:
public static final OffsetDateTimeSerializer INSTANCE = new OffsetDateTimeSerializer();
protected OffsetDateTimeSerializer() {
super(OffsetDateTime.class, dt -> dt.toInstant().toEpochMilli(),
OffsetDateTime::toEpochSecond, OffsetDateTime::getNano,
DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
// InstantSerializerBase.java
protected InstantSerializerBase(Class supportedType, ToLongFunction getEpochMillis,
ToLongFunction getEpochSeconds, ToIntFunction getNanoseconds,
DateTimeFormatter formatter)
{
super(supportedType, null);
defaultFormat = formatter;
this.getEpochMillis = getEpochMillis;
this.getEpochSeconds = getEpochSeconds;
this.getNanoseconds = getNanoseconds;
}
至此,OffsetDateTime的序列化流程就分析结束了。
时区信息丢失时区信息丢失是发生在OffsetDateTime的反序列化过程中,简单分析其反序列化过程;
反序列化的入口为GenericJackson2JsonRedisSerializer.deserialize,代码如下:
publicT deserialize(@Nullable byte[] source, Class type) throws SerializationException { Assert.notNull(type, "Deserialization type must not be null! Please provide Object.class to make use of Jackson2 default typing."); if (SerializationUtils.isEmpty(source)) { return null; } try { // 使用内部的ObjectMapper实例完成反序列化 return mapper.readValue(source, type); } catch (Exception ex) { throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex); } }
继续跟踪来到ObjectMapper._readMapAndClose方法,代码如下:
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
throws IOException
{
try (JsonParser p = p0) {
final Object result;
final DeserializationConfig cfg = getDeserializationConfig();
final DefaultDeserializationContext ctxt = createDeserializationContext(p, cfg);
JsonToken t = _initForReading(p, valueType);
if (t == JsonToken.VALUE_NULL) {
result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = null;
} else {
// 找到反序列化器并进行反序列化
result = ctxt.readRootValue(p, valueType,
_findRootDeserializer(ctxt, valueType), null);
ctxt.checkUnresolvedObjectId();
}
if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, valueType);
}
return result;
}
}
和序列化过程类似,反序列化也分为两步:
- 查找反序列化器;
- 反序列化;
和序列化器在SimpleSerializers.findSerializer方法中找到类似,反序列化器是在SimpleDeserializers.findBeanDeserializer方法中找到,代码如下:
public JsonDeserializer> findBeanDeserializer(JavaType type,
DeserializationConfig config, BeanDescription beanDesc)
throws JsonMappingException
{
return _find(type);
}
private final JsonDeserializer> _find(JavaType type) {
if (_classMappings == null) {
return null;
}
// 从_classMappings中找到
return _classMappings.get(new ClassKey(type.getRawClass()));
}
_classMappings赋值流程如下:
- JavaTimeModule调用父类SimpleModule的addDeserializer方法增加了OffsetDateTime的序列化器 InstantDeserializer.OFFSET_DATE_TIME;
- addDeserializer使用内部SimpleDeserializers实例缓存了类型和序列化器的映射关系;
- 这样在SimpleDeserializers中,就可以根据类型找到对应的序列化器了;
接下来就是反序列化器反序列化的实现了;
反序列化序列化的入口为DefaultDeserializationContext.readRootValue,此时反序列化器为InstantDeserializer.OFFSET_DATE_TIME,OffsetDateTime类反序列化真正的实现在InstantDeserializer.deserialize方法,代码如下:
public T deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
switch (parser.currentTokenId())
{
case JsonTokenId.ID_NUMBER_FLOAT:
return _fromDecimal(context, parser.getDecimalValue());
case JsonTokenId.ID_NUMBER_INT:
return _fromLong(context, parser.getLongValue());
case JsonTokenId.ID_STRING:
return _fromString(parser, context, parser.getText());
case JsonTokenId.ID_START_OBJECT:
return _fromString(parser, context,
context.extractScalarFromObject(parser, this, handledType()));
case JsonTokenId.ID_EMBEDDED_OBJECT:
return (T) parser.getEmbeddedObject();
case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(parser, context);
}
return _handleUnexpectedToken(context, parser, JsonToken.VALUE_STRING,
JsonToken.VALUE_NUMBER_INT, JsonToken.VALUE_NUMBER_FLOAT);
}
protected T _fromDecimal(DeserializationContext context, BigDecimal value)
{
FromDecimalArguments args =
DecimalUtils.extractSecondsAndNanos(value, (s, ns) -> new FromDecimalArguments(s, ns, getZone(context)));
return fromNanoseconds.apply(args);
}
fromNanoseconds的赋值见InstantDeserializer.OFFSET_DATE_TIME,代码如下:
public static final InstantDeserializer时区设置OFFSET_DATE_TIME = new InstantDeserializer<>( OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME, OffsetDateTime::from, a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId), a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId), (d, z) -> (d.isEqual(OffsetDateTime.MIN) || d.isEqual(OffsetDateTime.MAX) ? d : d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime()))), true ); // InstantDeserializer.java protected InstantDeserializer(Class supportedType, DateTimeFormatter formatter, Function parsedToValue, Function fromMilliseconds, Function fromNanoseconds, BiFunction adjust, boolean replaceZeroOffsetAsZ) { super(supportedType, formatter); this.parsedToValue = parsedToValue; this.fromMilliseconds = fromMilliseconds; this.fromNanoseconds = fromNanoseconds; this.adjust = adjust == null ? ((d, z) -> d) : adjust; this.replaceZeroOffsetAsZ = replaceZeroOffsetAsZ; _adjustToContextTZOverride = null; }
在反序列化流程中,时区相关的处理就是getZone(context),该方法底层实现为BaseSettings.getTimeZone方法,代码如下:
public TimeZone getTimeZone() {
TimeZone tz = _timeZone;
// 默认情况下_timeZone为空
return (tz == null) ? DEFAULT_TIMEZONE : tz;
}
private static final TimeZone DEFAULT_TIMEZONE = TimeZone.getTimeZone("UTC");
_timeZone的赋值流程如下:
- ObjectMapper声明了常量DEFAULT_BASE,其_timeZone属性为空;
- 在ObjectMapper构造函数中,默认使用DEFAULT_BASE构建了SerializationConfig和DeserializationConfig;
- getZone(context)通过DeserializationContext内部的DeserializationConfig实例获取到时区信息,那么就会得到DEFAULT_TIMEZONE;从而丢失时区信息;
至此,OffsetDateTime的序列化流程就分析结束了。
问题解决知道时区丢失的根源所在,那么问题自然引刃而解;
既然ObjectMapper默认没有设置时区信息,那么给ObjectMapper设置下当前时区信息便可,代码如下:
public ObjectMapper setTimeZone(TimeZone tz) {
_deserializationConfig = _deserializationConfig.with(tz);
_serializationConfig = _serializationConfig.with(tz);
return this;
}
当通过ObjectMapper.setTimeZone设置时区信息时,会自动更新SerializationConfig和DeserializationConfig的时区信息,这样在反序列化时就不会丢失时区信息了;
最终代码如下:
public class Demo {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule()).setTimeZone(TimeZone.getDefault());
objectMapper.activateDefaultTyping(
objectMapper.getPolymorphicTypeValidator(), DefaultTyping.NON_FINAL, As.PROPERTY);
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper);
OffsetDateTime now = OffsetDateTime.now();
System.out.println(now);
byte[] serialize = serializer.serialize(now);
OffsetDateTime deserialize = serializer.deserialize(serialize, OffsetDateTime.class);
System.out.println(deserialize);
}
}



