SpringBoot 调用 RedisTemplate 存储列表 redisTemplate.opsForList().rightPushAll(K var1, V... var2) 报错:xxxUser cannot be cast to java.lang.String
调试过程
通过本地调试,经过一个半天终于确定了是 redis 缓存信息有问题导致了任务调度没有达到预期。前面调试过没有问题的业务逻辑部分忽略,涉及的部分主要在对数据库一个规则表做缓存处理,因为业务需要会频繁使用。
大概问题是这样:刷新缓存的部分出错,导致规则没有写入到 redis,业务消费时找不到 redis 存储的规则,自然不会正常执行。所以关键点在数据写入到 redis。直接调用和调试都会报错。
调用方法:
redisTemplate.opsForList().rightPushAll(key, xxxUser)
报错信息:
xxxUser cannot be cast to java.lang.String
一开始以为是 opsForList().rightPushAll()
接受到第二个参数需要是 String 类型(从报错信息来看)。API rightPushAll(K var1, V... var2)
,K、V 都是 ListOperations<K, V>
定义的泛型类型。从自动装配注入的 RedisTemplate<String, Object> redisTemplate
可以看出,第二个参数应当为 Object 类型。而 xxxUser 传递进来 upCasting 向上转换 Object 应该是不成问题的。
百度搜索 RedisTemplate<String, Object> redis 使用 opsForList().rightPushAll 放入对象
,可以得到 AI 生成的一大串 demo 使用方法:
不得不赞一句百度 AI 真的很强大,以后一些基础性的对象使用方法,完全可以从它这里参考。
对照存入和读取方法都没有问题。然后又百度了 opsForList 存储对象报错 User cannot be cast to java.lang.String
,这里提供了一个解决方法:
ObjectMapper mapper = new ObjectMapper();
String objectAsString = mapper.writeValueAsString(yourObject);
但是读取的时候也要处理:
String valueFromList = redisTemplate.opsForList().leftPop(key);
ObjectMapper mapper = new ObjectMapper();
YourObjectType yourObject = mapper.readValue(valueFromList, YourObjectType.class);
在写入的部分修改了一下,发现存储的数据处理方式是不一样的。查看 gitlab 这个文件接近一年的时间都没有修改过,所以基本排除了当前 redis 读取代码存在问题的可能性。
回过头来发现,前面百度到的使用 demo 中,有提及到一些信息:
需要注意的是 RedisTemplate 默认使用的序列化器是 JdkSerializationRedisSerializer,它会将对象序列化后存储
如果你需要存储的是简单的字符串或数字,可以使用StringRedisTemplate,它使用 StringRedisSerializer 来序列化字符串。
如果你需要存储复杂对象,可以使用 JSON 序列化器,如 Jackson2JsonRedisSerializer,这样可以存储对象的 JSON 表示。
SpringBoot 使用本来就会有很多配置,正常都会有一个配置文件 xxConfig,是否是在配置文件里面定义了序列化器呢?进入到 RedisTemplate.class
中,按住 command 点击类名,查找使用的地方,找到了一个 RedisConfig.java
文件。
@Configuration
public class RedisConfig {
/**
* 注入 RedisConnectionFactory
*/
@Autowired
RedisConnectionFactory factory;
/**
* 实例化 RedisTemplate 对象
*
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(factory);
// 设置---非常重要********
ParserConfig.getGlobalInstance().addAccept("com.xxx");
return redisTemplate;
}
@Bean
public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
final Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(
Object.class);
...
return jackson2JsonRedisSerializer;
}
...
主要关注返回了 redisTemplate
对象的方法,设置了 keySerializer
、hashKeySerializer
使用 StringRedisSerializer
序列化器,同时设置了 hashValueSerializer
、valueSerializer
使用 Jackson2JsonRedisSerializer
序列化器。
从现有配置来看,配置文件一点儿问题都没有,还非常完善。好奇注入进来的 redisTemplate
有没有使用这些序列化器,通过断点调试,果然有问题,四项 keySerializer
、hashKeySerializer
、hashValueSerializer
、valueSerializer
使用的都是 StringRedisSerializer
。添加一项 redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer());
再次查看 defaultSerializer
设置是生效的。然后观察到这些实例对象@Id 值是不一样的,有先后,也就是说不是同一次初始化的。这说明序列化器有重新配置过。
解决
再次查找 RedisTemplate 类的使用里,找到了一个 RedisService 的组件,里面同样注入了 RestTemplate 对象,或者说使用的是同一个实例对象。这个是我从另一个项目里 copy 过来的,主要是从 redis 中读取和存储 token 用户信息。跟一般的使用不同的是,里面还加了一个自动装配的方法:
@Autowired(required = false)
public void setRedisTemplate(RedisTemplate redisTemplate) {
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
this.redisTemplate = redisTemplate;
}
@Autowired(required=false)
表示忽略当前要注入的 bean 如果有直接注入,没有就跳过,不会报错。所以这个方法也是会自动执行的。里面设置了四项为字符串序列化器,问题就出在这里了。redis 重复配置了。
注释掉对于 redis K、V 序列化器的重新设置,测试检查这部分 redis 操作没有报错,OK,问题解决。