使用JCache API的Redisson反序列化仅返回String引发类型转换异常
JCache + Redisson 存储LocalDateTime 反序列化异常解决方案
嘿,我来帮你分析下这个问题——你遇到的ClassCastException本质是Redisson在反序列化时,没能正确识别Redis中存储的字符串应该转换为LocalDateTime类型,下面拆解原因和解决办法:
问题回顾
你用JCache(JSR-107)结合Redisson存储<String, LocalDateTime>键值对,存储时Jackson能把LocalDateTime序列化成2018-01-23T11:59:34.997834这样的字符串,但调用cache.get()时却抛出类型转换异常,因为返回的是String而非预期的LocalDateTime。
你的核心代码如下:
测试代码
@Test public void getCacheInline() { Config redissonCfg = new Config(); redissonCfg .setCodec(new JsonJacksonCodec(buildObjectMapper())) .useSingleServer() .setAddress("redis://redis:6379"); MutableConfiguration<String, LocalDateTime> jcacheConfig = new MutableConfiguration<String, LocalDateTime>() .setTypes(String.class, LocalDateTime.class) .setExpiryPolicyFactory((Factory<ExpiryPolicy>) () -> new CreatedExpiryPolicy(new Duration(SECONDS, 100))); Configuration<String, LocalDateTime> configuration = RedissonConfiguration.fromConfig(redissonCfg, jcacheConfig); Cache<String, LocalDateTime> cache = cacheManager.createCache(CACHE_NAME, configuration); LocalDateTime expectedDateTime = LocalDateTime.now(); cache.put("testKey", expectedDateTime); // 抛出异常:java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.time.LocalDateTime LocalDateTime actualDateTime = cache.get("testKey"); assertThat(actualDateTime, is(equalTo(expectedDateTime))); }
ObjectMapper构建代码
private ObjectMapper buildObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(WRITE_DATES_AS_TIMESTAMPS, false); objectMapper.configure(READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false); objectMapper.setSerializationInclusion(NON_NULL); return objectMapper; }
核心原因
问题出在两个地方:
- 缺少类型元数据:你配置的
JsonJacksonCodec是通用的,序列化时只存储了LocalDateTime的字符串内容,没有附带类型信息,导致Jackson反序列化时不知道要转换成LocalDateTime,只能返回原始字符串。 - JCache类型声明未被有效利用:虽然你在
MutableConfiguration中调用了setTypes声明了键值类型,但Redisson的JCache实现需要Codec层面的配合才能把这些类型信息用于反序列化。
两种解决方案
方案一:给ObjectMapper添加类型信息支持
通过开启Jackson的默认类型序列化,让Redis中存储的JSON附带类型元数据,这样反序列化时就能明确目标类型:
修改你的buildObjectMapper()方法:
private ObjectMapper buildObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(WRITE_DATES_AS_TIMESTAMPS, false); objectMapper.configure(READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false); objectMapper.setSerializationInclusion(NON_NULL); // 新增:开启类型信息存储,告诉Jackson反序列化时的目标类型 objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.ALL, JsonTypeInfo.As.PROPERTY ); return objectMapper; }
方案二:使用指定键值类型的JsonJacksonCodec
如果不想全局开启类型信息,可以直接在创建JsonJacksonCodec时指定键和值的具体类型,让Redisson明确知道反序列化的目标类型:
修改你的Redisson配置部分:
@Test public void getCacheInline() { Config redissonCfg = new Config(); // 构建指定类型的Codec JsonJacksonCodec codec = new JsonJacksonCodec(String.class, LocalDateTime.class, buildObjectMapper()); redissonCfg.setCodec(codec) .useSingleServer() .setAddress("redis://redis:6379"); MutableConfiguration<String, LocalDateTime> jcacheConfig = new MutableConfiguration<String, LocalDateTime>() .setTypes(String.class, LocalDateTime.class) .setExpiryPolicyFactory((Factory<ExpiryPolicy>) () -> new CreatedExpiryPolicy(new Duration(SECONDS, 100))); Configuration<String, LocalDateTime> configuration = RedissonConfiguration.fromConfig(redissonCfg, jcacheConfig); Cache<String, LocalDateTime> cache = cacheManager.createCache(CACHE_NAME, configuration); LocalDateTime expectedDateTime = LocalDateTime.now(); cache.put("testKey", expectedDateTime); // 现在可以正确获取LocalDateTime LocalDateTime actualDateTime = cache.get("testKey"); assertThat(actualDateTime, is(equalTo(expectedDateTime))); }
这个方案更适合缓存类型固定的场景,不需要修改全局的ObjectMapper配置。
额外优化建议
你还可以在JCache配置中显式指定Codec属性,确保优先级最高:
jcacheConfig.setAttribute(RedissonConfiguration.REDISSON_CODEC_ATTRIBUTE, codec);
这样可以避免其他默认配置覆盖你的Codec设置。
内容的提问来源于stack exchange,提问作者Pedro R.
相关产品推荐
相关产品推荐

