这几天搭建一个项目,使用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);

更多推荐