原文网址:Spring-Data-Redis--解决java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx_IT利刃出鞘的博客-CSDN博客

简介

说明

本文介绍解决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 {@Autowiredprivate 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@Overridepublic 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();};}@Beanpublic 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();}@Beanpublic 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 causejava.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.demo.entity.Resultat 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 XXXobjectMapper.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@Overridepublic 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();};}@Beanpublic 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();}@Beanpublic 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 XXXobjectMapper.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次访问(成功)

http://localhost:8080/user/page?pageNo=1&pageSize=2

Spring-Data-Redis--解决java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx相关推荐

  1. 已解决java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Map异常的正确解决方法,亲测有效!!

    已解决java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Map异常的正确解决方法,亲测有效!! ...

  2. 报错java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String解决踩坑

    java.lang.ClassCaption: java.lang.Long cannot be cast to java.lang.String 问题背景 service传参调用mapper,报错: ...

  3. java lang ClassCastException java lang Integer cannot be ca

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 1.错误 ...

  4. ArrayMap java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]

    错误堆栈: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]at android. ...

  5. mysql:java.lang.ClassCastException: java.base/java.math.BigInteger出现类型转换错误

    一.错误信息 mysql:java.lang.ClassCastException: java.base/java.math.BigInteger cannot be 二.解决方法 这个就是因为你用的 ...

  6. java.lang.ClassCastException:android.widget.Button cannot be cast to android.widget.ImageView

    今天遇到一个错误也不知道怎么回事,上网搜了一下: 出现的问题是:java.lang.ClassCastException:android.widget.Button cannot be cast to ...

  7. java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Map

    1.错误描写叙述 java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Mapat service ...

  8. java.lang.ClassCastException: java.lang.Integer cannot be cast to java.math.BigD

    解决:将int类型转成BigDecimal类型就可以了 filtrationList.add(new Filtration(MatchType.EQ,BigDecimal.valueOf(0),&qu ...

  9. nested exception is java.lang.ClassCastException: java.lang String cannot be cast to java.util.Map

    今天来此记录下自己犯了多次的错误,就是在本地服务起了之后,在页面点击看效果报错,页面显示异常,后端报错,如下: falied to handle or send message;nested exce ...

最新文章

  1. kaggle (自杀分析)
  2. Python【每日一问】16
  3. QT开发(十)——QT输入组件
  4. 学习笔记900天总结
  5. 数据库数据过长避免_为什么要避免使用商业数据科学平台
  6. Windows上快速在指定目录打开cmd.exe命令行的方法
  7. php 统计 系统设计,求解统计系统设计的一些技术方案和实现
  8. 深交所与工信部签署优质中小企业上市培育战略合作协议
  9. Codeforces Round #246 (Div. 2) D. Prefixes and Suffixe 后缀数组
  10. HDOJ(HDU) 2097 Sky数(进制)
  11. 科学计算器java_科学计算器的Java实现
  12. VS2013 工具包修复记录
  13. 数分下第5讲(8.3节) 二次曲面方程和曲线方程
  14. Dubbo源码解析-Dubbo协议解析
  15. 街头篮球服务器维护中,雷冥竟然有这能力? 《街头篮球》五一稀有角色能力解析...
  16. Java中的观察者模式详解
  17. SoundHound:根据哼唱的旋律找到你想要的歌曲
  18. vue使用高德地图关键字搜索功能的实例代码(踩坑经验)
  19. 「大学必读」计算机专业学生一定要学好哪些课程?
  20. python注销代码_python怎么注销代码_如何从一个简单的web应用程序注销。在CherryPy,Python中...

热门文章

  1. c语言程序设计机房机位预约系统,c语言课程设计 机房机位预定系统 绝对正确,附源代码.doc...
  2. VUE 创建共通js 以及引用该js的共通方法
  3. 台式计算机i7处理器,【Intel 酷睿i7 6代台式机参数】Intel 酷睿i7 6代台式机系列CPU参数-ZOL中关村在线...
  4. 连接mysql数据库的三个接口_数据库的三种接口
  5. 微信内置浏览器内置方法WeixinJSBridge
  6. 简单聊聊程序员的健康问题
  7. uni-app实现扫码
  8. 凡人如何让自己的工作独一无二
  9. cocos2dx---游戏摇杆类(观察者模式控制英雄移动)
  10. 计算机网络对我们来说意味着什么意思,全网通手机的普及对我们来说意味着什么?...