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

解决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获取值时指定编解码器。所以这个问题影响比较小。

相关文章:

  • Java基础 5.21
  • 如何用ipmitool修改FRU信息?
  • [Vue]路由基础使用和路径传参
  • 小米汽车二期工厂下月将竣工,产能提升助力市场拓展
  • Linux虚拟文件系统(2)
  • 第P10周:Pytorch实现车牌识别
  • 程序设计基础----排序(2)
  • 在局域网(LAN)中查看设备的 IP 地址
  • 批量创建文件夹并创建并列子文件夹
  • 【Java基础笔记vlog】Java中常见的几种数组排序算法汇总详解
  • Springboot2
  • 两足行走机器人:仿生设计与智能控制的技术革命
  • STM32之串口通信蓝牙(BLE)
  • vue+srpingboot实现多文件导出
  • Qt enabled + geometry 属性(2)
  • matlab加权核范数最小化图像去噪
  • 软件设计师“排序算法”真题考点分析——求三连
  • word通配符表
  • 远程连接的公私钥加密
  • Sign签证绕过
  • 刷题网站建设/百度推广一级代理商名单
  • java做网站的权限管理/网站流量排行
  • 工程建设信息官方网站/博客网站