banner
NEWS LETTER

springboot中使用@Cacheable结合redis和FastJson遇到的类型转换问题

Scroll down

昨天在写代码的时候碰到了一个问题,在使用@Cacheable注解实现redis缓存时,第一次调用方法可以正常获取数据,这时将缓存数据存入redis中,一切正常。但是在第二次调用这个方法的时候,也就是会从redis中取数据时程序报错,一个类型转换错误

java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to 'xxx'

这里让我很迷惑,明明是在第一次调用的时候存入redis的数据,为什么在取出来的时候会有类型转换错误?在网上搜集相关报错内容的时候大部分都是使用redisTemplate的opsForValue方法进行存写的时候出现类似问题,并没有看到和我一样的报错情况。

在我的redisConfig中也配置了CacheManager对应的序列化方式,使用的序列化器为FastJsonRedisSerializer,后来在一篇帖子中找到了解决方式
参考链接:[CSDN]-springboot2.x使用redis作为缓存(使用fastjson序列化的方式,并调试反序列化异常)

根据文章内容,需要使用自定义的FastJsonRedisSerializer(名称无所谓在配置类中使用自己定义的serializer就好)利用fastjson完成序列化和反序列化,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
/*
要实现对象的缓存,定义自己的序列化和反序列化器。使用阿里的fastjson来实现的比较多。
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}

@Override
public byte[] serialize(T t) throws SerializationException {
if (null == t) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}

@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (null == bytes || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}

然后在配置类redisConfig中定义使用自定义的序列化器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//配置cacheManager
@Bean
@Primary
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<Object>(Object.class);

//配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
.disableCachingNullValues();

RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
ParserConfig.getGlobalInstance().addAccept("com.yahoo.common.utils.R");
ParserConfig.getGlobalInstance().addAccept("com.baomidou.mybatisplus.extension.plugins.pagination.Page");
ParserConfig.getGlobalInstance().addAccept("com.yahoo.clothes.entity.Clothes");
ParserConfig.getGlobalInstance().addAccept("com.yahoo.user.entity.User");
return cacheManager;
}

需要注意的是ParserConfig.getGlobalInstance().addAccept("类名或包")这里需要设置白名单,否则会报错com.alibaba.fastjson.JSONException: autoType is not support,比如我这里使用的是自定义的泛型返回类R<T>,需要将泛型T的类也放入白名单,比如我上边写到的clothes或者user类,或者直接将一个包下的所有类都添加白名单
ParserConfig.getGlobalInstance().addAccept("com.yahoo.user.entity.")

有关autoType的具体解释参考:[CSDN]-解决com.alibaba.fastjson.JSONException: autoType is not support

其他文章
cover
碎碎念(一)
  • 22/12/04
  • 19:04
  • 随笔