在学习spring-boot 2.x 的过程中,学习使用 redis 时在将 Object类型存入 redis 出现 java.util.LinkedHashMap cannot be cast to com.xxx 遇到的问题 :
在使用缓存注解时需要修改序列化方式。需要重写 cacheManager ,由于学习的spring-boot视频版本为 1.x 发现 spring-boot 1.x 与 2.x 的重写方式有很大差别!
spring-boot 1.x 版本源码如下:
@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setUserPrefix(true);
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
spring-boot 2.x 版本源码如下:
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(resourceLoader.getClassLoader()));
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet(cacheNames));
}
return (RedisCacheManager)this.customizerInvoker.customize(builder.build());
}
spring-boot 1.x 版本重写方式:
@Bean
public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setUserPrefix(true);
return cacheManager;
}
通过测试发现,通过代码的方式保存对象到 redis 默认会使用 jdk 序列化的方式:
@Test
public void test02(){
Employee empById = employeeMapper.getEmpById(1);
//默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
//redisTemplate.opsForValue().set("emp01",empById);
/**
* 1.将数据以json的方式保存
* 1).自己将对象转为JSON 使用JSON转换工具
* 2).redisTemplate默认的序列化规则
*/
empRedisTemplate.opsForValue().set("emp01",empById);
}
需要重写 RedisTemplate 修改 RedisTemplate 默认的序列化方式:
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//设置默认的序列化方式
template.setDefaultSerializer(new Jackson2JsonRedisSerializer(Object.class));
return template;
}
如果是通过注解
@Cacheable 标注的方法执行之前先检查缓存中有没有这个数据,默认按照参数
* 的值作为key去查询缓存如果没有就运行该方法,并将结果放入缓存,以后再调用
* 可以直接使用缓存中的数据
的方式,则需要重写下述spring-boot 2.x 版本重写的方式。
spring-boot 2.x 版本重写方式:
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//初始化json的序列化方式
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer);
//设置 value 的序列化方式为 jackson2JsonRedisSerializer
RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
//设置默认超过期时间是100秒
defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofSeconds(100));
//初始化RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//初始化RedisCacheManager
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
//设置白名单---非常重要********
/*
使用fastjson的时候:序列化时将class信息写入,反解析的时候,
fastjson默认情况下会开启autoType的检查,相当于一个白名单检查,
如果序列化信息中的类路径不在autoType中,
反解析就会报com.alibaba.fastjson.JSONException: autoType is not support的异常
可参考 https://blog.csdn.net/u012240455/article/details/80538540
*/
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
return cacheManager;
}
通过测试发现,如果我们使用注解的方式存入缓存,第一次不重写上述 spring-boot 2.x 版本 cacheManager 。
@Cacheable(/*cacheNames = "emp"*//*,keyGenerator = "myKeyGenerator",condition = "#id > 1",unless = "#id==2"*/)
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
return employeeMapper.getEmpById(id);
}
则默认使用的是jdk的序列化方式。存入redis中的对象如下图所示:
而后第二次我们重写了上述 spring-boot 2.x 版本 cacheManager 以后,我们再来通过注解来调用
@Cacheable(/*cacheNames = "emp"*//*,keyGenerator = "myKeyGenerator",condition = "#id > 1",unless = "#id==2"*/)
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
return employeeMapper.getEmpById(id);
}
@Cacheable 标注的方法的值如果被缓存后,下次会直接查询缓存。
由于我们重写了 cacheManager 以后,把默认的序列化方式改为了 JSON 格式的,缓存在反序列化时也使用 JSON 格式查询缓存,而之前使用 JDK 序列化的方式存入缓存,则会出现下列乱码现象:
刚接触 spring-boot,以上均为手打,如有错误望各位大佬多多指点。
参考资料: