You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 12:57:36