redis 序列化对象问题

本文涉及的产品
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
简介: redis 序列化对象问题

redis 序列化

背景

最近在使用redis的发布订阅模式时,订阅类接收到的是字符串,习惯性的用JSON将字符串转成对象,结果就是各种报错,刚开始想不通,通过redis可视化工具看到的明明是JSON,把结果复制出来也是能通过JSON测试的,为什么通过发布订阅获取到的结果就不能转成对象呢?

追根溯源

为什么通过下面代码能正确获取到数据呢?

@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 普通缓存获取
*/
public <T> T get(String key){
    return key == null ? null : (T) redisTemplate.opsForValue().get(key);
}

所以,查看get方法源码也许能找到答案。

通过查看找到DefaultValueOperations类,DefaultValueOperations是ValueOperations的默认实现类,ValueOperations是对value进行操作的接口。

class DefaultValueOperations<K, V> extends AbstractOperations<K, V> implements ValueOperations<K, V>

查看DefaultValueOperations#get方法

@Override
  public V get(Object key) {
    return execute(new ValueDeserializingRedisCallback(key) {
      @Override
      protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
        return connection.get(rawKey);
      }
    }, true);
  }

可以看到value的反序列化是通过ValueDeserializingRedisCallback实现的

// utility methods for the template internal methods
  abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {
    private Object key;
    public ValueDeserializingRedisCallback(Object key) {
      this.key = key;
    }
    //进行value的反序列化
    public final V doInRedis(RedisConnection connection) {
      byte[] result = inRedis(rawKey(key), connection);
      //value的反序列化
      return deserializeValue(result);
    }
    @Nullable
    protected abstract byte[] inRedis(byte[] rawKey, RedisConnection connection);
  }

最终对value的反序列化是deserializeValue方法

V deserializeValue(byte[] value) {
  if (valueSerializer() == null) {
    return (V) value;
  }
  return (V) valueSerializer().deserialize(value);
}

valueSerializer()是通过RedisTemplate赋值的。

RedisSerializer valueSerializer() {
  return template.getValueSerializer();
}

所以最终反序列化的源头还是在RedisTemplate里面。

RedisTemplate

RedisTemplate 部分源码:

public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
  //是否试用默认Serializer
  private boolean enableDefaultSerializer = true;
  //默认Serializer
  private @Nullable RedisSerializer<?> defaultSerializer;
  private @Nullable ClassLoader classLoader;
  @SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
  @SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
  @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
  @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
  private RedisSerializer<String> stringSerializer = RedisSerializer.string();
  /*
   * (non-Javadoc)
   * @see org.springframework.data.redis.core.RedisAccessor#afterPropertiesSet()
   */
  @Override
  public void afterPropertiesSet() {
    super.afterPropertiesSet();
    //是否使用默认的
    boolean defaultUsed = false;
    if (defaultSerializer == null) {
      //初始化defaultSerializer 
      defaultSerializer = new JdkSerializationRedisSerializer(
          classLoader != null ? classLoader : this.getClass().getClassLoader());
    }
    if (enableDefaultSerializer) {
      if (keySerializer == null) {
        keySerializer = defaultSerializer;
        defaultUsed = true;
      }
      if (valueSerializer == null) {
        valueSerializer = defaultSerializer;
        defaultUsed = true;
      }
      if (hashKeySerializer == null) {
        hashKeySerializer = defaultSerializer;
        defaultUsed = true;
      }
      if (hashValueSerializer == null) {
        hashValueSerializer = defaultSerializer;
        defaultUsed = true;
      }
    }
    if (enableDefaultSerializer && defaultUsed) {
      Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
    }
    if (scriptExecutor == null) {
      this.scriptExecutor = new DefaultScriptExecutor<>(this);
    }
    //初始化完成
    initialized = true;
  }
}

RedisTemplate在初始化时会对为null的属性进行赋值操作,除了stringSerializer是使用StringRedisSerializer进行序列化之外,其他的Serializer都是使用默认的JdkSerializationRedisSerializer。

如果


RedisSerializer

JdkSerializationRedisSerializer 是实现了RedisSerializer接口的,如果想对redis进行序列化和反序列化也可以实现RedisSerializer接口。

redis在对对象进行序列化的时候会有一个@class字段表示这个对象所属类的全限定名。

RedisSerializer 提供了四个Serializer的实例对象:


  • JdkSerializationRedisSerializer:序列化的类必须实现java.io.Serializable接口
  • GenericJackson2JsonRedisSerializer:Jackson 序列化
  • StringRedisSerializer:String对象的序列化
  • ByteArrayRedisSerializer:字节数组


下图中出了fastjson,其他都是spring-data-redis 的子类实现,有兴趣的可以去看看。

image.png

RedisSerializer部分源码:

/**
   * Obtain a {@link RedisSerializer} using java serialization with the given {@link ClassLoader}.<br />
   * <strong>Note:</strong> Ensure that your domain objects are actually {@link java.io.Serializable serializable}.
   *
   * @param classLoader the {@link ClassLoader} to use for deserialization. Can be {@literal null}.
   * @return new instance of {@link RedisSerializer}. Never {@literal null}.
   * @since 2.1
   */
  static RedisSerializer<Object> java(@Nullable ClassLoader classLoader) {
    return new JdkSerializationRedisSerializer(classLoader);
  }
  /**
   * Obtain a {@link RedisSerializer} that can read and write JSON using
   * <a href="https://github.com/FasterXML/jackson-core">Jackson</a>.
   *
   * @return never {@literal null}.
   * @since 2.1
   */
  static RedisSerializer<Object> json() {
    return new GenericJackson2JsonRedisSerializer();
  }
  /**
   * Obtain a simple {@link java.lang.String} to {@literal byte[]} (and back) serializer using
   * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8} as the default {@link java.nio.charset.Charset}.
   *
   * @return never {@literal null}.
   * @since 2.1
   */
  static RedisSerializer<String> string() {
    return StringRedisSerializer.UTF_8;
  }
  /**
   * Obtain a {@link RedisSerializer} that passes thru {@code byte[]}.
   *
   * @return never {@literal null}.
   * @since 2.2
   */
  static RedisSerializer<byte[]> byteArray() {
    return ByteArrayRedisSerializer.INSTANCE;
  }

替换默认RedisSerializer

如果想替换掉默认RedisSerializer子类,只需要在注入RedisTemplate的时候给DefaultSerializer赋值即可,其他的参数也可以根据需要在注入RedisTemplate的时候赋值

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String , Object> redisTemplate(){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //修改默认序列化
        template.setDefaultSerializer(RedisSerializer.json());
        return template;
    }
}

总结

Redis为了将数据跨平台存储和通过网络传输,将对象序列化为字节数组,在获取到Redis数据时就需要反序列化为对象。

至此,上述不能通过JSON转对象的原因就找到了,通过发布订阅获取到的对象是个字节数组,通过JSON转对象的形式是行不通的,那些redis可视化工具也是将字节数组转为可视化的JSON数据。

如果想得到发布/订阅的数据就需要反序列化。

可通过以下代码进行反序列化:

  @Autowired
    private RedisTemplate redisTemplate;
    public <T> T deserialize(String data){
        RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
        Object deserialize = valueSerializer.deserialize(data.getBytes(StandardCharsets.UTF_8));
        return deserialize == null ? null : (T) deserialize;
    }

end

能力一般,水平有限,如有错误,请多指出。

如果对你有用点个关注给个赞呗

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
4月前
|
NoSQL 测试技术 Go
【Golang】国密SM2公钥私钥序列化到redis中并加密解密实战_sm2反编(1)
【Golang】国密SM2公钥私钥序列化到redis中并加密解密实战_sm2反编(1)
|
7天前
|
存储 Java
Java编程中的对象序列化与反序列化
【9月更文挑战第12天】在Java的世界里,对象序列化与反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何通过实现Serializable接口来标记一个类的对象可以被序列化,并探索ObjectOutputStream和ObjectInputStream类的使用,以实现对象的写入和读取。我们还将讨论序列化过程中可能遇到的问题及其解决方案,确保你能够高效、安全地处理对象序列化。
|
22天前
|
存储 Java
Java编程中的对象序列化与反序列化
【8月更文挑战第28天】在Java世界中,对象序列化与反序列化是数据持久化和网络传输的关键技术。本文将深入浅出地探讨这一过程,带你领略其背后的原理及应用,让你的程序在数据的海洋中自由航行。
|
17天前
|
存储 Java
Java编程中的对象序列化与反序列化
【9月更文挑战第2天】在Java的世界里,对象序列化和反序列化就像是给数据穿上了一件隐形的斗篷。它们让数据能够轻松地穿梭于不同的系统之间,无论是跨越网络还是存储在磁盘上。本文将揭开这层神秘的面纱,带你领略序列化和反序列化的魔法,并展示如何通过代码示例来施展这一魔法。
14 0
|
1月前
|
存储 安全 Java
揭秘Java序列化神器Serializable:一键解锁对象穿越时空的超能力,你的数据旅行不再受限,震撼登场!
【8月更文挑战第4天】Serializable是Java中的魔术钥匙,开启对象穿越时空的能力。作为序列化的核心,它让复杂对象的复制与传输变得简单。通过实现此接口,对象能被序列化成字节流,实现本地存储或网络传输,再通过反序列化恢复原状。尽管使用方便,但序列化过程耗时且存在安全风险,需谨慎使用。
35 7
|
2月前
|
存储 Java
JaveSE—IO流详解:对象输入输出流(序列化及反序列化)
JaveSE—IO流详解:对象输入输出流(序列化及反序列化)
|
2月前
|
存储 Java 开发者
Java中的对象序列化详解
Java中的对象序列化详解
|
3月前
|
NoSQL Redis
redis使用jackson序列化数据配置文件
redis使用jackson序列化数据配置文件
|
2月前
|
存储 JSON 数据库
项目管理定义问题之什么是序列化大对象的值对象数据库形态
项目管理定义问题之什么是序列化大对象的值对象数据库形态
|
2月前
|
JSON NoSQL Java
Redis18的Java客户端-StringRedisTemplate,序列化存在的问题,使用StringRedisTemplate解决序列化的方法
Redis18的Java客户端-StringRedisTemplate,序列化存在的问题,使用StringRedisTemplate解决序列化的方法