升级spring-data-redis等组件后出现@class类型ID缺失异常
解决升级spring-data-redis/jackson-databind后Jackson反序列化缺少@class类型ID的问题
升级spring-data-redis、jedis、jackson-databind至最新版本后,未修改任何POJO代码,出现如下反序列化异常:
build 28-Jul-2022 18:45:32 at [Source: (byte[])"["java.util.LinkedHashSet",[{"@class":IntegrationConfig","entities":["java.util.Collections$UnmodifiableRandomAccessList",[{"name":"launch""nested_a"[truncated 339059 bytes]; line: 1, column: 307] (through reference chain: java.util.LinkedHashSet[0]->IntegrationConfig["onboarding_config"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class com.aw.dpa.common.pipeline.config.OnboardingConfig]: missing type id property '@class' (for POJO property 'onboarding_config') build 28-Jul-2022 18:45:32 at [Source: (byte[])"["java.util.LinkedHashSet",[{"@class":"IntegrationConfig","entities":["java.util.Collections$UnmodifiableRandomAccessList",[{"name":"launch","nested_a"[truncated 339059 bytes]; line: 1, column: 307] (through reference chain: java.util.LinkedHashSet[0]->IntegrationConfig["onboarding_config"]) build 28-Jul-2022 18:45:32 at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:164) build 28-Jul-2022 18:45:32 at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:142) build 28-Jul-2022 18:45:32 at org.springframework.data.redis.core.AbstractOperations.deserializeHashValue(AbstractOperations.java:380) build 28-Jul-2022 18:45:32 at org.springframework.data.redis.core.DefaultHashOperations.get(DefaultHashOperations.java:57) build 28-Jul-2022 18:45:32 at org.springframework.data.redis.core.DefaultBoundHashOperations.get(DefaultBoundHashOperations.java:65) build 28-Jul-2022 18:45:32 at org.springframework.data.redis.support.collections.DefaultRedisMap.get(DefaultRedisMap.java:160) build 28-Jul-2022 18:45:32 at org.springframework.data.redis.support.collections.DefaultRedisMap.putIfAbsent(DefaultRedisMap.java:281) build 28-Jul-2022 18:45:32 at com.aw.dpa.distributable.redis.map.RedisMapImpl.putIfAbsent(RedisMapImpl.java:80) build 28-Jul-2022 18:45:32 at PipelineConfigUpdateManager.doPostConstruct(PipelineConfigUpdateManager.java:100) build 28-Jul-2022 18:45:32 at jdk.internal.reflect.GeneratedMethodAccessor429.invoke(Unknown Source) build 28-Jul-2022 18:45:32 at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) build 28-Jul-2022 18:45:32 at java.base/java.lang.reflect.Method.invoke(Method.java:566) build 28-Jul-2022 18:45:32 at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) build 28-Jul-2022 18:45:32 at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) build 28-Jul-2022 18:45:32 at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) build 28-Jul-2022 18:45:32 ... 60 common frames omitted build 28-Jul-2022 18:45:32 Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class OnboardingConfig]: missing type id property '@class' (for POJO property 'onboarding_config') build 28-Jul-2022 18:45:32 at [Source: (byte[])"["java.util.LinkedHashSet",[{"@class":"IntegrationConfig","entities":["java.util.Collections$UnmodifiableRandomAccessList",[{"name":"launch","nested_a"[truncated 339059 bytes]; line: 1, column: 307] (through reference chain: java.util.LinkedHashSet[0]->IntegrationConfig["onboarding_config"])
问题原因
新版本的jackson-databind或spring-data-redis调整了默认的类型序列化策略:
- GenericJackson2JsonRedisSerializer默认的ObjectMapper配置变更,对非抽象类也开始要求类型标识
@class - 旧版本存储在Redis中的数据未包含
onboarding_config字段的@class属性,导致反序列化时无法解析类型
解决方案
1. 自定义GenericJackson2JsonRedisSerializer的ObjectMapper配置
通过手动配置ObjectMapper,指定类型序列化规则,或者关闭特定类型的类型检查:
@Bean public GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer() { ObjectMapper mapper = new ObjectMapper(); // 配置仅对非final类添加类型标识,适配大多数场景 mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); // 如果OnboardingConfig没有子类,直接关闭该类型的类型检查 mapper.configOverride(OnboardingConfig.class) .setTypeIdResolver(new TypeIdResolver() { @Override public JsonTypeInfo.Id getMechanism() { return JsonTypeInfo.Id.NONE; } @Override public void init(JavaType baseType) {} @Override public String idFromValue(Object value) { return null; } @Override public String idFromValueAndType(Object value, Class<?> suggestedType) { return null; } @Override public JavaType typeFromId(DatabindContext context, String id) throws IOException { return context.constructType(OnboardingConfig.class); } @Override public String idFromBaseType() { return null; } @Override public String getDescForKnownTypeIds() { return null; } @Override public JsonTypeInfo.As getTypeInclusion() { return JsonTypeInfo.As.PROPERTY; } }); return new GenericJackson2JsonRedisSerializer(mapper); }
2. 给POJO添加Jackson类型注解
如果OnboardingConfig存在子类,需要保留类型信息,直接在对应的字段或类上添加注解:
- 在
IntegrationConfig的onboarding_config字段上添加:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") private OnboardingConfig onboarding_config;
- 或者在
OnboardingConfig类上添加(适用于该类有子类的场景):
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") public class OnboardingConfig { // 类成员变量及方法 }
3. 兼容Redis旧数据的临时方案
如果Redis中已存储大量无@class字段的旧数据,不想重新写入,可以配置Jackson在解析时使用默认类型:
@Bean public GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer() { ObjectMapper mapper = new ObjectMapper(); mapper.setDefaultTyping(new DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL) { @Override protected TypeIdResolver idResolver(MapperConfig<?> config, JavaType baseType, Collection<NamedType> subtypes, boolean forSer, boolean forDeser) { TypeIdResolver resolver = super.idResolver(config, baseType, subtypes, forSer, forDeser); // 针对OnboardingConfig类型,自定义解析器,缺失@class时返回默认类型 if (baseType.getRawClass() == OnboardingConfig.class) { return new TypeIdResolver() { @Override public JsonTypeInfo.Id getMechanism() { return JsonTypeInfo.Id.CLASS; } @Override public void init(JavaType baseType) {} @Override public String idFromValue(Object value) { return value.getClass().getName(); } @Override public String idFromValueAndType(Object value, Class<?> suggestedType) { return idFromValue(value); } @Override public JavaType typeFromId(DatabindContext context, String id) throws IOException { try { return context.constructType(Class.forName(id)); } catch (ClassNotFoundException e) { // 缺失@class时,直接返回OnboardingConfig类型 return context.constructType(OnboardingConfig.class); } } @Override public String idFromBaseType() { return null; } @Override public String getDescForKnownTypeIds() { return null; } @Override public JsonTypeInfo.As getTypeInclusion() { return JsonTypeInfo.As.PROPERTY; } }; } return resolver; } }); return new GenericJackson2JsonRedisSerializer(mapper); }
内容的提问来源于stack exchange,提问作者woggle
相关产品推荐
相关产品推荐

