Spring-Data-Redis--解决java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx
Posted IT利刃出鞘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring-Data-Redis--解决java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx相关的知识,希望对你有一定的参考价值。
简介
说明
本文介绍解决Spring-Data-Redis的“java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx”报错的方法。
出现的场景
SpringBoot项目中使用Redis来进行缓存。把数据放到缓存中时没有问题,但从缓存中取出来反序列化为对象时报错:“java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx”。(xxx为反序列化的目标对象对应的类。)
只有这个类里有其他对象字段才会报这个问题,如果这个类里都是初始的类型(比如:Integer,String)则不会报这个错误。
只要用到Redis序列化反序列化的地方都会遇到这个问题,比如:RedisTemplate,Redisson,@Cacheable注解等。
问题复现
业务代码
Controller
package com.example.demo.controller;
import com.example.demo.entity.Result;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class UserController
@Autowired
private UserService userService;
@GetMapping("page")
public Result page(int pageNo, int pageSize)
return userService.page(pageNo, pageSize);
Service
接口
package com.example.demo.service;
import com.example.demo.entity.Result;
public interface UserService
Result page(int pageNo, int pageSize);
实现
package com.example.demo.service.impl;
import com.example.demo.constant.RedisConstant;
import com.example.demo.entity.Result;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
public class UserServiceImpl implements UserService
private final List<User> allUsers = Arrays.asList(
new User(1L, "Tony1", 20),
new User(2L, "Tony2", 18),
new User(3L, "Tony3", 30),
new User(4L, "Tony4", 25),
new User(5L, "Tony5", 28)
);
@Override
@Cacheable(cacheNames = "userPageCache")
public Result<List<User>> page(int pageNo, int pageSize)
String format = String.format("pageNo: %s, pageSize: %s", pageNo, pageSize);
System.out.println("从数据库中读数据。" + format);
int from = (pageNo - 1) * pageSize;
int to = Math.min(allUsers.size(), (pageNo) * pageSize);
List<User> users = new ArrayList<>(allUsers.subList(from, to));
return new Result<List<User>>().data(users);
Entity
User
package com.example.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
// 必须要有无参构造函数。因为Redis反序列化为对象时要用到
@NoArgsConstructor
public class User
private Long id;
private String userName;
private Integer age;
Result
package com.example.demo.entity;
import lombok.Data;
@Data
public class Result<T>
private boolean success = true;
private int code = 1000;
private String message;
private T data;
public Result()
public Result(boolean success)
this.success = success;
public Result<T> success(boolean success)
Result<T> result = new Result<>(success);
if (success)
result.code = 1000;
else
result.code = 1001;
return result;
public Result<T> success()
return success(true);
public Result<T> failure()
return success(false);
/**
* @param code @link ResultCode#getCode()
*/
public Result<T> code(int code)
this.code = code;
return this;
public Result<T> message(String message)
this.message = message;
return this;
public Result<T> data(T data)
this.data = data;
return this;
Redis配置代码
package com.example.demo.config;
import com.example.demo.constant.RedisConstant;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import org.springframework.util.StringUtils;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
/**
* 重写缓存Key生成策略。
* 包名+方法名+参数列表。防止缓存Key冲突
*/
@Bean
@Override
public KeyGenerator keyGenerator()
return (target, method, params) ->
// 存放最终结果
StringBuilder resultStringBuilder = new StringBuilder("cache:key:");
// 执行方法所在的类
resultStringBuilder.append(target.getClass().getName()).append(".");
// 执行的方法名称
resultStringBuilder.append(method.getName()).append("(");
// 存放参数
StringBuilder paramStringBuilder = new StringBuilder();
for (Object param : params)
if (param == null)
paramStringBuilder.append("java.lang.Object[null],");
else
paramStringBuilder
.append(param.getClass().getName())
.append("[")
.append(String.valueOf(param))
.append("],");
if (StringUtils.hasText(paramStringBuilder.toString()))
// 去掉最后的逗号
String trimLastComma = paramStringBuilder.substring(0, paramStringBuilder.length() - 1);
resultStringBuilder.append(trimLastComma);
return resultStringBuilder.append(")").toString();
;
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory)
Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.initialCacheNames(configurationMap.keySet())
.withInitialCacheConfigurations(configurationMap)
.cacheDefaults(config)
.build();
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory factory)
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(keySerializer());
template.setValueSerializer(valueSerializer());
template.setHashKeySerializer(keySerializer());
template.setHashValueSerializer(valueSerializer());
template.afterPropertiesSet();
return template;
private RedisSerializer<String> keySerializer()
return new StringRedisSerializer();
private RedisSerializer<Object> valueSerializer()
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
测试
第1次访问(成功)
http://localhost:8080/user/page?pageNo=1&pageSize=2
结果:成功访问,结果或存入Redis
第2次访问(失败)
http://localhost:8080/user/page?pageNo=1&pageSize=2
结果:报错
后端输出:
2022-01-11 14:59:23.805 ERROR 68468 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.demo.entity.Result] with root cause
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.demo.entity.Result
at com.sun.proxy.$Proxy56.page(Unknown Source) ~[na:na]
at com.example.demo.controller.UserController.page(UserController.java:18) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
......
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.46.jar:9.0.46]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_201]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_201]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.46.jar:9.0.46]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_201]
原因分析
SpringBoot 的缓存使用 jackson 来做数据的序列化与反序列化,如果默认使用 Object 作为序列化与反序列化的类型,则其只能识别 java 基本类型,遇到复杂类型时,jackson 就会先序列化成 LinkedHashMap ,然后再尝试强转为所需类别,这样大部分情况下会强转失败。
问题解决
修改RedisTemplate这个bean的valueSerializer,设置默认类型。
关键代码:
private RedisSerializer<Object> valueSerializer()
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 此项必须配置,否则如果序列化的对象里边还有对象,会报如下错误:
// java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
objectMapper.activateDefaultTyping(
objectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
旧版写法:
// objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
解决后的测试
代码
只修改Redis配置类
package com.example.demo.config;
import com.example.demo.constant.RedisConstant;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import org.springframework.util.StringUtils;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
/**
* 重写缓存Key生成策略。
* 包名+方法名+参数列表。防止缓存Key冲突
*/
@Bean
@Override
public KeyGenerator keyGenerator()
return (target, method, params) ->
// 存放最终结果
StringBuilder resultStringBuilder = new StringBuilder("cache:key:");
// 执行方法所在的类
resultStringBuilder.append(target.getClass().getName()).append(".");
// 执行的方法名称
resultStringBuilder.append(method.getName()).append("(");
// 存放参数
StringBuilder paramStringBuilder = new StringBuilder();
for (Object param : params)
if (param == null)
paramStringBuilder.append("java.lang.Object[null],");
else
paramStringBuilder
.append(param.getClass().getName())
.append("[")
.append(String.valueOf(param))
.append("],");
if (StringUtils.hasText(paramStringBuilder.toString()))
// 去掉最后的逗号
String trimLastComma = paramStringBuilder.substring(0, paramStringBuilder.length() - 1);
resultStringBuilder.append(trimLastComma);
return resultStringBuilder.append(")").toString();
;
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory)
Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.initialCacheNames(configurationMap.keySet())
.withInitialCacheConfigurations(configurationMap)
.cacheDefaults(config)
.build();
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory factory)
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(keySerializer());
template.setValueSerializer(valueSerializer());
template.setHashKeySerializer(keySerializer());
template.setHashValueSerializer(valueSerializer());
template.afterPropertiesSet();
return template;
private RedisSerializer<String> keySerializer()
return new StringRedisSerializer();
private RedisSerializer<Object> valueSerializer()
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 此项必须配置,否则如果序列化的对象里边还有对象,会报如下错误:
// java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
objectMapper.activateDefaultTyping(
objectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
// 旧版写法:
// objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
测试
第1次访问(成功)
http://localhost:8080/user/page?pageNo=1&pageSize=2
可以看到,在写入Redis时,会带上类全名。这样在反序列化时就能成功了。
第2次访问(成功)
以上是关于Spring-Data-Redis--解决java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx的主要内容,如果未能解决你的问题,请参考以下文章
java之redis篇(spring-data-redis整合)
java之redis篇(spring-data-redis整合) (转)
java之redis篇(spring-data-redis整合)