• 售前

  • 售后

热门帖子
入门百科

Spring-Data-Redis--解决java.lang.ClassCastException: java.util.LinkedH

[复制链接]
想不到918 显示全部楼层 发表于 2022-1-13 07:15:25 |阅读模式 打印 上一主题 下一主题
原文网址: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
  1. package com.example.demo.controller;
  2. import com.example.demo.entity.Result;
  3. import com.example.demo.service.UserService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. @RestController
  9. @RequestMapping("user")
  10. public class UserController {
  11.     @Autowired
  12.     private UserService userService;
  13.     @GetMapping("page")
  14.     public Result page(int pageNo, int pageSize) {
  15.         return userService.page(pageNo, pageSize);
  16.     }
  17. }
复制代码
Service
接口
  1. package com.example.demo.service;
  2. import com.example.demo.entity.Result;
  3. public interface UserService {
  4.     Result page(int pageNo, int pageSize);
  5. }
复制代码
实现
  1. package com.example.demo.service.impl;
  2. import com.example.demo.constant.RedisConstant;
  3. import com.example.demo.entity.Result;
  4. import com.example.demo.entity.User;
  5. import com.example.demo.service.UserService;
  6. import org.springframework.cache.annotation.Cacheable;
  7. import org.springframework.stereotype.Service;
  8. import java.util.ArrayList;
  9. import java.util.Arrays;
  10. import java.util.List;
  11. @Service
  12. public class UserServiceImpl implements UserService {
  13.     private final List<User> allUsers = Arrays.asList(
  14.             new User(1L, "Tony1", 20),
  15.             new User(2L, "Tony2", 18),
  16.             new User(3L, "Tony3", 30),
  17.             new User(4L, "Tony4", 25),
  18.             new User(5L, "Tony5", 28)
  19.     );
  20.     @Override
  21.     @Cacheable(cacheNames = "userPageCache")
  22.     public Result<List<User>> page(int pageNo, int pageSize) {
  23.         String format = String.format("pageNo: %s, pageSize: %s", pageNo, pageSize);
  24.         System.out.println("从数据库中读数据。" + format);
  25.         int from = (pageNo - 1) * pageSize;
  26.         int to = Math.min(allUsers.size(), (pageNo) * pageSize);
  27.         List<User> users = new ArrayList<>(allUsers.subList(from, to));
  28.         return new Result<List<User>>().data(users);
  29.     }
  30. }
复制代码
Entity
User
  1. package com.example.demo.entity;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. @Data
  6. @AllArgsConstructor
  7. // 必须要有无参构造函数。因为Redis反序列化为对象时要用到
  8. @NoArgsConstructor
  9. public class User {
  10.     private Long id;
  11.     private String userName;
  12.     private Integer age;
  13. }
复制代码
Result
  1. package com.example.demo.entity;
  2. import lombok.Data;
  3. @Data
  4. public class Result<T> {
  5.     private boolean success = true;
  6.     private int code = 1000;
  7.     private String message;
  8.     private T data;
  9.     public Result() {
  10.     }
  11.     public Result(boolean success) {
  12.         this.success = success;
  13.     }
  14.     public Result<T> success(boolean success) {
  15.         Result<T> result = new Result<>(success);
  16.         if (success) {
  17.             result.code = 1000;
  18.         } else {
  19.             result.code = 1001;
  20.         }
  21.         return result;
  22.     }
  23.     public Result<T> success() {
  24.         return success(true);
  25.     }
  26.     public Result<T> failure() {
  27.         return success(false);
  28.     }
  29.     /**
  30.      * @param code {@link ResultCode#getCode()}
  31.      */
  32.     public Result<T> code(int code) {
  33.         this.code = code;
  34.         return this;
  35.     }
  36.     public Result<T> message(String message) {
  37.         this.message = message;
  38.         return this;
  39.     }
  40.     public Result<T> data(T data) {
  41.         this.data = data;
  42.         return this;
  43.     }
  44. }
复制代码
Redis配置代码

  1. package com.example.demo.config;
  2. import com.example.demo.constant.RedisConstant;
  3. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  4. import com.fasterxml.jackson.annotation.JsonTypeInfo;
  5. import com.fasterxml.jackson.annotation.PropertyAccessor;
  6. import com.fasterxml.jackson.databind.ObjectMapper;
  7. import org.springframework.cache.CacheManager;
  8. import org.springframework.cache.annotation.CachingConfigurerSupport;
  9. import org.springframework.cache.annotation.EnableCaching;
  10. import org.springframework.cache.interceptor.KeyGenerator;
  11. import org.springframework.context.annotation.Bean;
  12. import org.springframework.context.annotation.Configuration;
  13. import org.springframework.data.redis.cache.RedisCacheConfiguration;
  14. import org.springframework.data.redis.cache.RedisCacheManager;
  15. import org.springframework.data.redis.connection.RedisConnectionFactory;
  16. import org.springframework.data.redis.core.RedisTemplate;
  17. import org.springframework.data.redis.serializer.*;
  18. import org.springframework.util.StringUtils;
  19. import java.time.Duration;
  20. import java.util.HashMap;
  21. import java.util.Map;
  22. @Configuration
  23. @EnableCaching
  24. public class RedisConfig extends CachingConfigurerSupport {
  25.     /**
  26.      * 重写缓存Key生成策略。
  27.      * 包名+方法名+参数列表。防止缓存Key冲突
  28.      */
  29.     @Bean
  30.     @Override
  31.     public KeyGenerator keyGenerator() {
  32.         return (target, method, params) -> {
  33.             // 存放最终结果
  34.             StringBuilder resultStringBuilder = new StringBuilder("cache:key:");
  35.             // 执行方法所在的类
  36.             resultStringBuilder.append(target.getClass().getName()).append(".");
  37.             // 执行的方法名称
  38.             resultStringBuilder.append(method.getName()).append("(");
  39.             // 存放参数
  40.             StringBuilder paramStringBuilder = new StringBuilder();
  41.             for (Object param : params) {
  42.                 if (param == null) {
  43.                     paramStringBuilder.append("java.lang.Object[null],");
  44.                 } else {
  45.                     paramStringBuilder
  46.                             .append(param.getClass().getName())
  47.                             .append("[")
  48.                             .append(String.valueOf(param))
  49.                             .append("],");
  50.                 }
  51.             }
  52.             if (StringUtils.hasText(paramStringBuilder.toString())) {
  53.                 // 去掉最后的逗号
  54.                 String trimLastComma = paramStringBuilder.substring(0, paramStringBuilder.length() - 1);
  55.                 resultStringBuilder.append(trimLastComma);
  56.             }
  57.             return resultStringBuilder.append(")").toString();
  58.         };
  59.     }
  60.     @Bean
  61.     public CacheManager cacheManager(RedisConnectionFactory factory) {
  62.         Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
  63.         RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  64.                 .entryTtl(Duration.ofMinutes(5))
  65.                 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
  66.                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
  67.                 .disableCachingNullValues();
  68.         return RedisCacheManager.builder(factory)
  69.                 .initialCacheNames(configurationMap.keySet())
  70.                 .withInitialCacheConfigurations(configurationMap)
  71.                 .cacheDefaults(config)
  72.                 .build();
  73.     }
  74.     @Bean
  75.     public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory factory) {
  76.         RedisTemplate<Object, Object> template = new RedisTemplate<>();
  77.         template.setConnectionFactory(factory);
  78.         template.setKeySerializer(keySerializer());
  79.         template.setValueSerializer(valueSerializer());
  80.         template.setHashKeySerializer(keySerializer());
  81.         template.setHashValueSerializer(valueSerializer());
  82.         template.afterPropertiesSet();
  83.         return template;
  84.     }
  85.     private RedisSerializer<String> keySerializer() {
  86.         return new StringRedisSerializer();
  87.     }
  88.     private RedisSerializer<Object> valueSerializer() {
  89.         Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
  90.                 new Jackson2JsonRedisSerializer<>(Object.class);
  91.         ObjectMapper objectMapper = new ObjectMapper();
  92.         objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  93.         jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  94.         return jackson2JsonRedisSerializer;
  95.     }
  96. }
复制代码
测试

第1次访问(成功)
http://localhost:8080/user/page?pageNo=1&pageSize=2
结果:成功访问,结果或存入Redis


第2次访问(失败)
http://localhost:8080/user/page?pageNo=1&pageSize=2
结果:报错

后端输出:
  1. <code>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
  2. java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.demo.entity.Result
  3.         at com.sun.proxy.$Proxy56.page(Unknown Source) ~[na:na]
  4.         at com.example.demo.controller.UserController.page(UserController.java:18) ~[classes/:na]
  5.         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
  6.         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]
  7.         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
  8.         at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]
  9.         at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
  10.         at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
  11.         ......
  12.         at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) [tomcat-embed-core-9.0.46.jar:9.0.46]
  13.         at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.46.jar:9.0.46]
  14.         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_201]
  15.         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_201]
  16.         at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.46.jar:9.0.46]
  17.         at java.lang.Thread.run(Thread.java:748) [na:1.8.0_201]
复制代码
原因分析

        SpringBoot 的缓存使用 jackson 来做数据的序列化与反序列化,如果默认使用 Object 作为序列化与反序列化的类型,则其只能识别 java 基本类型,遇到复杂类型时,jackson 就会先序列化成 LinkedHashMap ,然后再尝试强转为所需类别,这样大部分情况下会强转失败。
问题解决

修改RedisTemplate这个bean的valueSerializer,设置默认类型。
关键代码:
  1. private RedisSerializer<Object> valueSerializer() {
  2.     Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
  3.             new Jackson2JsonRedisSerializer<>(Object.class);
  4.     ObjectMapper objectMapper = new ObjectMapper();
  5.     objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  6.     // 此项必须配置,否则如果序列化的对象里边还有对象,会报如下错误:
  7.     //     java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
  8.     objectMapper.activateDefaultTyping(
  9.             objectMapper.getPolymorphicTypeValidator(),
  10.             ObjectMapper.DefaultTyping.NON_FINAL,
  11.             JsonTypeInfo.As.PROPERTY);
  12.     旧版写法:
  13.     // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  14.     jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  15.     return jackson2JsonRedisSerializer;
  16. }
复制代码
解决后的测试

代码

只修改Redis配置类
  1. package com.example.demo.config;
  2. import com.example.demo.constant.RedisConstant;
  3. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  4. import com.fasterxml.jackson.annotation.JsonTypeInfo;
  5. import com.fasterxml.jackson.annotation.PropertyAccessor;
  6. import com.fasterxml.jackson.databind.ObjectMapper;
  7. import org.springframework.cache.CacheManager;
  8. import org.springframework.cache.annotation.CachingConfigurerSupport;
  9. import org.springframework.cache.annotation.EnableCaching;
  10. import org.springframework.cache.interceptor.KeyGenerator;
  11. import org.springframework.context.annotation.Bean;
  12. import org.springframework.context.annotation.Configuration;
  13. import org.springframework.data.redis.cache.RedisCacheConfiguration;
  14. import org.springframework.data.redis.cache.RedisCacheManager;
  15. import org.springframework.data.redis.connection.RedisConnectionFactory;
  16. import org.springframework.data.redis.core.RedisTemplate;
  17. import org.springframework.data.redis.serializer.*;
  18. import org.springframework.util.StringUtils;
  19. import java.time.Duration;
  20. import java.util.HashMap;
  21. import java.util.Map;
  22. @Configuration
  23. @EnableCaching
  24. public class RedisConfig extends CachingConfigurerSupport {
  25.     /**
  26.      * 重写缓存Key生成策略。
  27.      * 包名+方法名+参数列表。防止缓存Key冲突
  28.      */
  29.     @Bean
  30.     @Override
  31.     public KeyGenerator keyGenerator() {
  32.         return (target, method, params) -> {
  33.             // 存放最终结果
  34.             StringBuilder resultStringBuilder = new StringBuilder("cache:key:");
  35.             // 执行方法所在的类
  36.             resultStringBuilder.append(target.getClass().getName()).append(".");
  37.             // 执行的方法名称
  38.             resultStringBuilder.append(method.getName()).append("(");
  39.             // 存放参数
  40.             StringBuilder paramStringBuilder = new StringBuilder();
  41.             for (Object param : params) {
  42.                 if (param == null) {
  43.                     paramStringBuilder.append("java.lang.Object[null],");
  44.                 } else {
  45.                     paramStringBuilder
  46.                             .append(param.getClass().getName())
  47.                             .append("[")
  48.                             .append(String.valueOf(param))
  49.                             .append("],");
  50.                 }
  51.             }
  52.             if (StringUtils.hasText(paramStringBuilder.toString())) {
  53.                 // 去掉最后的逗号
  54.                 String trimLastComma = paramStringBuilder.substring(0, paramStringBuilder.length() - 1);
  55.                 resultStringBuilder.append(trimLastComma);
  56.             }
  57.             return resultStringBuilder.append(")").toString();
  58.         };
  59.     }
  60.     @Bean
  61.     public CacheManager cacheManager(RedisConnectionFactory factory) {
  62.         Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
  63.         RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  64.                 .entryTtl(Duration.ofMinutes(5))
  65.                 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
  66.                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
  67.                 .disableCachingNullValues();
  68.         return RedisCacheManager.builder(factory)
  69.                 .initialCacheNames(configurationMap.keySet())
  70.                 .withInitialCacheConfigurations(configurationMap)
  71.                 .cacheDefaults(config)
  72.                 .build();
  73.     }
  74.     @Bean
  75.     public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory factory) {
  76.         RedisTemplate<Object, Object> template = new RedisTemplate<>();
  77.         template.setConnectionFactory(factory);
  78.         template.setKeySerializer(keySerializer());
  79.         template.setValueSerializer(valueSerializer());
  80.         template.setHashKeySerializer(keySerializer());
  81.         template.setHashValueSerializer(valueSerializer());
  82.         template.afterPropertiesSet();
  83.         return template;
  84.     }
  85.     private RedisSerializer<String> keySerializer() {
  86.         return new StringRedisSerializer();
  87.     }
  88.     private RedisSerializer<Object> valueSerializer() {
  89.         Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
  90.                 new Jackson2JsonRedisSerializer<>(Object.class);
  91.         ObjectMapper objectMapper = new ObjectMapper();
  92.         objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  93.         // 此项必须配置,否则如果序列化的对象里边还有对象,会报如下错误:
  94.         //     java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
  95.         objectMapper.activateDefaultTyping(
  96.                 objectMapper.getPolymorphicTypeValidator(),
  97.                 ObjectMapper.DefaultTyping.NON_FINAL,
  98.                 JsonTypeInfo.As.PROPERTY);
  99.         // 旧版写法:
  100.         // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  101.         jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  102.         return jackson2JsonRedisSerializer;
  103.     }
  104. }
复制代码
测试

第1次访问(成功)
http://localhost:8080/user/page?pageNo=1&pageSize=2

 
可以看到,在写入Redis时,会带上类全名。这样在反序列化时就能成功了。 
第2次访问(成功)
http://localhost:8080/user/page?pageNo=1&pageSize=2





来源:https://blog.caogenba.net/feiying0canglang/article/details/122427607
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作