redis 使用序列化问题解决
这几天搭建一个项目,使用redis,在使用的时候出现一个序列化的错误 java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.lang.String (java.lang.Long and java.lang.String are in module java.base of loader 'bootstrap')

项目中使用的redis框架是redisson:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.26.0</version>
</dependency>
问题描述
项目中使用到lua脚本,需要批量设置给定key的过期时间,lua 脚本如下:
local keys = KEYS
local ttl = tonumber(ARGV[1])
for i, key in ipairs(keys) do
redis.call('EXPIRE', key, ttl)
end
return true
java 中调用脚本设置过期时间的代码如下:expireSeconds=120s
private void batchExpire(Collection<String> keys, long expireSeconds) {
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(Boolean.class);
redisScript.setScriptText(RedisScript.BATCH_EXPIRE);
redisTemplate.execute(redisScript, Lists.newArrayList(keys), expireSeconds);
}
调试跟踪代码发现在实在key和args序列化行出错,具体代码和出错参数如下:org.springframework.data.redis.core.script.DefaultScriptExecutor#execute(org.springframework.data.redis.core.script.RedisScript<T>, org.springframework.data.redis.serializer.RedisSerializer<?>, org.springframework.data.redis.serializer.RedisSerializer<T>, java.util.List<K>, java.lang.Object...)

其中keysAndArgs方法的代码如下,主要是序列化key和args的,上面的调试代码可以看出,key和args的序列化类都是用的是StringRedisSerializer,该类只能序列化值是字符串类型的数据,对于其他数据类型就无能为力了,如过期时间是长整型的数据。
protected byte[][] keysAndArgs(RedisSerializer argsSerializer, List<K> keys, Object[] args) {
final int keySize = keys != null ? keys.size() : 0;
byte[][] keysAndArgs = new byte[args.length + keySize][];
int i = 0;
if (keys != null) {
for (K key : keys) {
if (keySerializer() == null && key instanceof byte[] keyBytes) {
keysAndArgs[i++] = keyBytes;
} else {
keysAndArgs[i++] = keySerializer().serialize(key);
}
}
}
for (Object arg : args) {
if (argsSerializer == null && arg instanceof byte[] argBytes) {
keysAndArgs[i++] = argBytes;
} else {
keysAndArgs[i++] = argsSerializer.serialize(arg);
}
}
return keysAndArgs;
}
所以不能使用redisson框架自动配置的序列化器,自带的配置在org.redisson.spring.starter.RedissonAutoConfiguration具体代码不展示,感兴趣的可以自己查看一下源码。

解决方案:
自定义RedisTemplate bean,修改序列化类:
@Configuration
public class RedisConfig {
/**
* bean name = redisTemplate,覆盖默认的redisTemplate,默认使用jdk的序列化
*
* @param factory
* @return RedisTemplate<String, Object>
* @see RedissonAutoConfiguration
*/
@Primary
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
StringRedisSerializer keySerializer = new StringRedisSerializer();
//可以个性化序列化,默认会添加@class 属性,为了反射找到具体的类
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
template.setKeySerializer(keySerializer);
template.setValueSerializer(serializer);
template.setHashKeySerializer(keySerializer);
template.setHashValueSerializer(serializer);
return template;
}
@Primary
@Bean(name = "stringRedisTemplate")
public StringRedisTemplate stringTemplate(RedisConnectionFactory factory) {
//不用redisson 自带的,序列化,在执行的时候,value非字符串会报错
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(factory);
StringRedisSerializer keySerializer = new StringRedisSerializer();
//可以个性化序列化,默认会添加@class 属性,为了反射找到具体的类
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
template.setKeySerializer(keySerializer);
template.setValueSerializer(serializer);
template.setHashKeySerializer(keySerializer);
template.setHashValueSerializer(serializer);
return template;
}
}
上面选择的key任然选择的是String类型的序列化器(因为我们绝大多数key都是String,不考虑非字符串),除了上面提到的两种序列化器,Spring和一些第三方框架页提供了写一些其他的序列化器,可以选择适合自己的序列化器,如果没有没有合适的也可以自己写一个:

说明:
对于系统自带的,StringRedisTemplate,如果只是操作key value 数据类型的化,如果value值不是String,idea还会有一些提示,但对于操作复杂数据了下,如Map,Set或lua脚本等值非String类型的时,运行时(java的弱鸡泛型编译不会报错)就会报无法序列化的错,在代码编写阶段很难意识到问题,很容易出错(如果测试不细致)。如下面代码:
stringRedisTemplate.opsForHash().put("map:1", "2",user);
更多推荐


所有评论(0)