当前位置: 首页 > article >正文

解决RedisTemplate的json反序列泛型丢失问题

背景

在使用redisTemplate操作redis时我们针对对象的序列化通常将序列化成json存储到redis。一般如下配置

@Bean  
@ConditionalOnMissingBean  
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory,  ObjectProvider<RedisTemplateCustomizer> customizers) {  RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();  redisTemplate.setConnectionFactory(redisConnectionFactory);  StringRedisSerializer keySerializer = new StringRedisSerializer();  redisTemplate.setKeySerializer(keySerializer);  redisTemplate.setHashKeySerializer(keySerializer);  ObjectMapper objectMapper = ObjectMapperWrapper.getObjectMapper();  GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);  redisTemplate.setValueSerializer(valueSerializer);  redisTemplate.setHashValueSerializer(valueSerializer);  customizers.orderedStream().forEach((customizer) -> customizer.customize(redisTemplate));  return redisTemplate;  
}

使用GenericJackson2JsonRedisSerializer进行配置。但是这种方式会引发一个问题当进行反序列时如果是对象则会报错例如:
SecurityUserInfo o = (SecurityUserInfo) redisTemplateObject.opsForValue().get(key); 会报linkedHashMap无法转成具体的类型。因为序列化的json没有包含类型信息。只能按照默认的方式转换成linkedHashMap

解决方案

方案一

将jackson库的ObjectMapper序列化时带上类型信息mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); 但是这种方式会有几个缺点:

  • 增加redis存储,因为带上的类型信息
  • 可读性下降,类型信息会混淆在json中
  • 如果多个应用读写redis会增加理解成本
    所以此方案并不推荐

方案二

不使用activateDefaultTyping,查询的时候使用Object接收,然后使用mapper.convertValue方法转换。缺点是多一次序列化的操作,影响性能

方案三

曲线救国,使用ScopeValue将类型信息传递给RedisTemplate的序列化器(也可以用ThreadLocal),当反序列化时动态获取其类型。这个方式需要增加几个类,使用方式变化一下

  • 增加helper类
import lombok.extern.slf4j.Slf4j;  import java.util.concurrent.Callable;  /**  * @author wxl  */@Slf4j  
@SuppressWarnings("all")  
public class RedisDeserializeHelper {  public static final ScopedValue<Class<?>> TYPE = ScopedValue.newInstance();  public static <R> R call(Class<R> clazz, Callable<Object> op) {  try {  Object call = ScopedValue.where(TYPE, clazz).call(op);  if (call == null) {  return null;  }  if (clazz.isAssignableFrom(call.getClass())) {  return (R) call;  }  return (R) call;  } catch (Exception e) {  log.error("redis deserialize failed", e);  throw new RuntimeException(e);  }  }  public static Class<?> get() {  return TYPE.get();  }  
}
  • 增加自定义编解码器
import com.fasterxml.jackson.databind.ObjectMapper;  
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;  
import org.springframework.data.redis.serializer.SerializationException;  /**  * @author wxl  */public class SofastGenericJackson2JsonRedisSerializer extends GenericJackson2JsonRedisSerializer {  public SofastGenericJackson2JsonRedisSerializer(ObjectMapper objectMapper) {  super(objectMapper);  }  @Override  public Object deserialize(byte[] source) throws SerializationException {  Class<?> clazz = RedisDeserializeHelper.get();  if (clazz != null) {  return deserialize(source, clazz);  }  return super.deserialize(source);  }  @Override  public <T> T deserialize(byte[] source, Class<T> type) throws SerializationException {  return super.deserialize(source, type);  }  
}
  • 调用方式
SecurityUserInfo securityUserInfo = RedisDeserializeHelper.call(SecurityUserInfo.class, () -> redisTemplateObject.opsForValue().get(key));

总结

  • 如果性能要求不高推荐使用方案二,对性能要求高可以参考方案三
  • 另外对于redisson的序列化也会遇到相同的问题,但是redisson可以再从redis获取值时指定编解码器。所以这个问题影响比较小。
http://www.lryc.cn/news/2386518.html

相关文章:

  • 【Elasticsearch】创建别名的几种方式
  • 【JAVA】中文我该怎么排序?
  • 《C 语言字符串操作从入门到实战(下篇):strncpy/strncat/strstr 等函数原理与实现》
  • 百度飞桨PaddleOCR 3.0开源发布 OCR精度跃升13%
  • Xilinx 7Series\UltraScale 在线升级FLASH STARTUPE2和STARTUPE3使用
  • 数字孪生驱动的离散制造智能升级:架构设计与工程实践
  • 9.4在 VS Code 中配置 Maven
  • mmaction2——tools文件夹下
  • 新能源汽车充电桩资源如何利用资源高效配置?
  • python 程序实现了毫米波大规模MIMO系统中的信道估计对比实验
  • NTFS0x90属性和0xa0属性和0xb0属性的一一对应关系是index_entry中的index_node中VCN和runlist和bitmap
  • PDF 编辑批量拆分合并OCR 识别
  • LeetCode --- 450周赛
  • SpringBoot中消息转换器的选择
  • (初级)前端初学者入门指南:HTML5与CSS3核心知识详解
  • 基于点标注的弱监督目标检测方法研究
  • 【RichTextEditor】 【分析2】RichTextEditor设置文字内容背景色
  • 超越OpenAI CodeX的软件工程智能体:Jules
  • Qt实战教程:设计并实现一个结构清晰、功能完整的桌面应用
  • 轻量化MEC终端 特点
  • NIST提出新型安全指标:识别潜在被利用漏洞
  • List介绍
  • 正则表达式全解:一文学会正则表达式【附在线正则表达式练习网站】
  • Nginx-详解(二)
  • 解决 IntelliJ IDEA 配置文件中文被转义问题
  • MCP、MCPHub、A2A、AG-UI概述
  • 计算机视觉与深度学习 | Python实现CEEMDAN-ISOS-VMD-GRU-ARIMA时间序列预测(完整源码和数据)
  • [Linux]磁盘分区及swap交换空间
  • JAVA面向对象——对象和类的基本语法
  • Linux常见指令合集+知识点