问题背景
在使用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
依赖,如下:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
重新运行后,报错依然;
除了需要引入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<Object> findTypedValueSerializer(Class<?> valueType,
boolean cache, BeanProperty property)
throws JsonMappingException
{
// 先从已知类型中查找
JsonSerializer<Object> ser = _knownSerializers.typedValueSerializer(valueType);
if (ser != null) {
return ser;
}
// 再从序列化器缓存中查找
ser = _serializerCache.typedValueSerializer(valueType);
if (ser != null) {
return ser;
}
// 还找不到,那么就根据valueType进行创建了
ser = findValueSerializer(valueType, property);
TypeSerializer typeSer = _serializerFactory.createTypeSerializer(_config,
_config.constructType(valueType));
if (typeSer != null) {
typeSer = typeSer.forProperty(property);
ser = new TypeWrappedSerializer(typeSer, ser);
}
if (cache) {
_serializerCache.addTypedSerializer(valueType, ser);
}
return ser;
}
继续跟踪,来到创建序列化器的方法BeanSerializerFactory.createSerializer
,代码如下:
public JsonSerializer<Object> createSerializer(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<Object>) 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<Object,Object> conv = beanDesc.findSerializationConverter();
if (conv == null) {
// 不存在,则直接创建并返回
return (JsonSerializer<Object>) _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<T> supportedType, ToLongFunction<T> getEpochMillis,
ToLongFunction<T> getEpochSeconds, ToIntFunction<T> getNanoseconds,
DateTimeFormatter formatter)
{
super(supportedType, null);
defaultFormat = formatter;
this.getEpochMillis = getEpochMillis;
this.getEpochSeconds = getEpochSeconds;
this.getNanoseconds = getNanoseconds;
}
至此,OffsetDateTime
的序列化流程就分析结束了。
时区信息丢失
时区信息丢失是发生在OffsetDateTime
的反序列化过程中,简单分析其反序列化过程;
反序列化的入口为GenericJackson2JsonRedisSerializer.deserialize
,代码如下:
public <T> T deserialize(@Nullable byte[] source, Class<T> 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<OffsetDateTime> 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<T> supportedType,
DateTimeFormatter formatter,
Function<TemporalAccessor, T> parsedToValue,
Function<FromIntegerArguments, T> fromMilliseconds,
Function<FromDecimalArguments, T> fromNanoseconds,
BiFunction<T, ZoneId, T> 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);
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/4764.html