Redis缓存数据反序列化失败5分钟搞定Jackson类型信息配置Redis作为高性能的内存数据库在Java应用中被广泛用于缓存数据。然而许多开发者在将对象存入Redis后取出来时却遇到了java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx这样的错误。这通常是由于序列化时丢失了类型信息导致的。本文将深入解析问题根源并提供一套完整的解决方案。1. 为什么Redis反序列化会失败当使用Redis存储Java对象时对象需要先被序列化为字节流或字符串。常见的序列化方式有JDK序列化、JSON序列化等。其中JSON序列化因其可读性和跨语言特性而广受欢迎。但JSON序列化有一个潜在问题默认情况下Jackson等JSON库在序列化时不会包含对象的类型信息。这意味着序列化时User对象被转换为JSON字符串如{name:张三,age:30}反序列化时Jackson看到这个JSON不知道它原来是什么类型只能将其反序列化为最通用的LinkedHashMap这就是为什么你会看到LinkedHashMap cannot be cast to User这样的错误。2. 解决方案激活默认类型信息Jackson提供了activateDefaultTyping方法来解决这个问题。这个方法会在序列化的JSON中添加类型信息确保反序列化时能还原为正确的Java类型。2.1 核心配置代码ObjectMapper objectMapper new ObjectMapper(); objectMapper.activateDefaultTyping( objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY );这段代码做了三件事创建一个ObjectMapper实例调用activateDefaultTyping方法启用类型信息配置类型信息以属性的形式嵌入JSON中2.2 配置参数详解参数类型说明polymorphicTypeValidatorPolymorphicTypeValidator类型验证器用于安全反序列化DefaultTyping.NON_FINAL枚举指定哪些类型需要包含类型信息JsonTypeInfo.As.PROPERTY枚举指定类型信息如何嵌入JSON其中DefaultTyping有几个可选值NON_FINAL: 为非final类添加类型信息OBJECT_AND_NON_CONCRETE: 为对象和非具体类型添加类型信息NON_CONCRETE_AND_ARRAYS: 为非具体类型和数组添加类型信息EVERYTHING: 为所有类型添加类型信息提示生产环境推荐使用NON_FINAL既能满足大多数需求又不会过度暴露类型信息。3. 完整Redis配置示例下面是一个完整的Spring Boot Redis配置类包含了RedisTemplate和CacheManager的配置Configuration Slf4j public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // 配置序列化器 ObjectMapper objectMapper new ObjectMapper(); objectMapper.activateDefaultTyping( objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); Jackson2JsonRedisSerializerObject serializer new Jackson2JsonRedisSerializer(Object.class); serializer.setObjectMapper(objectMapper); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); return template; } Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { ObjectMapper objectMapper new ObjectMapper(); objectMapper.activateDefaultTyping( objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); Jackson2JsonRedisSerializerObject serializer new Jackson2JsonRedisSerializer(Object.class); serializer.setObjectMapper(objectMapper); RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(RedisSerializationContext.SerializationPair .fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(serializer)); return RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); } }4. 实际效果对比配置前后Redis中存储的数据会有明显不同配置前存储的JSON:{ name: 张三, age: 30 }配置后存储的JSON:{ class: com.example.User, name: 张三, age: 30 }可以看到配置后的JSON多了一个class属性这就是Jackson添加的类型信息。当从Redis读取数据时Jackson会根据这个属性将数据反序列化为正确的User对象而不是LinkedHashMap。5. 进阶配置与注意事项5.1 自定义类型属性名默认情况下类型信息使用class作为属性名。如果你想自定义这个名称可以这样配置JsonTypeInfo(use JsonTypeInfo.Id.CLASS, include JsonTypeInfo.As.PROPERTY, property type) public class User { // 类定义 }这样生成的JSON会变成{ type: com.example.User, name: 张三, age: 30 }5.2 安全注意事项启用类型信息会带来一定的安全风险因为攻击者可能构造恶意类型进行攻击。建议使用最新的Jackson版本它包含了针对反序列化攻击的保护配置PolymorphicTypeValidator来限制允许反序列化的类型不要反序列化不受信任的数据5.3 性能影响添加类型信息会增加JSON的大小但对大多数应用来说影响可以忽略不计。如果确实关心性能可以考虑使用更短的属性名如c代替class对值进行压缩存储对特别大的对象考虑其他序列化方式6. 常见问题排查即使配置了类型信息有时还是会遇到问题。以下是几个常见场景及解决方法泛型类型反序列化失败对于ListUser这样的泛型类型除了配置activateDefaultTyping外还需要确保:使用TypeReference来反序列化或者为集合类型注册专门的序列化器final类无法反序列化activateDefaultTyping默认不会为final类添加类型信息。解决方法将类改为非final或者使用JsonTypeInfo注解显式标注多态类型处理对于继承体系中的类型需要确保父类使用JsonTypeInfo注解或者全局配置DefaultTyping.OBJECT_AND_NON_CONCRETE第三方库中的类对于不能修改的第三方类可以通过MixIn机制添加类型信息JsonTypeInfo(use Id.CLASS, include As.PROPERTY, property class) public abstract class ThirdPartyClassMixIn {} objectMapper.addMixIn(ThirdPartyClass.class, ThirdPartyClassMixIn.class);7. 替代方案比较除了Jackson的activateDefaultTyping还有其他几种处理类型信息的方式方案优点缺点activateDefaultTyping全局配置简单可能包含不必要的类型信息JsonTypeInfo注解精确控制需要修改类定义自定义序列化器完全控制实现复杂JDK序列化自动处理类型不可读不跨语言对于大多数Spring应用activateDefaultTyping是最平衡的选择。它只需要一次配置就能解决大部分反序列化问题。