redis 序列化对象问题

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 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
目录
相关文章
|
3月前
|
缓存 安全 PHP
PHP中的魔术方法与对象序列化
本文将深入探讨PHP中的魔术方法,特别是与对象序列化和反序列化相关的__sleep()和__wakeup()方法。通过实例解析,帮助读者理解如何在实际应用中有效利用这些魔术方法,提高开发效率和代码质量。
|
2月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
3月前
|
存储 消息中间件 NoSQL
Redis 数据结构与对象
【10月更文挑战第15天】在实际应用中,需要根据具体的业务需求和数据特点来选择合适的数据结构,并合理地设计数据模型,以充分发挥 Redis 的优势。
62 8
|
3月前
|
JSON 前端开发 数据格式
前端的全栈之路Meteor篇(五):自定义对象序列化的EJSON介绍 - 跨设备的对象传输
EJSON是Meteor框架中扩展了标准JSON的库,支持更多数据类型如`Date`、`Binary`等。它提供了序列化和反序列化功能,使客户端和服务器之间的复杂数据传输更加便捷高效。EJSON还支持自定义对象的定义和传输,通过`EJSON.addType`注册自定义类型,确保数据在两端无缝传递。
|
4月前
|
JSON NoSQL Java
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
这篇文章介绍了在Java中使用Redis客户端的几种方法,包括Jedis、SpringDataRedis和SpringBoot整合Redis的操作。文章详细解释了Jedis的基本使用步骤,Jedis连接池的创建和使用,以及在SpringBoot项目中如何配置和使用RedisTemplate和StringRedisTemplate。此外,还探讨了RedisTemplate序列化的两种实践方案,包括默认的JDK序列化和自定义的JSON序列化,以及StringRedisTemplate的使用,它要求键和值都必须是String类型。
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
|
3月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
3月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。
|
3月前
|
JSON 缓存 NoSQL
Redis 在线查看序列化对象技术详解
Redis 在线查看序列化对象技术详解
51 2
|
2月前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
54 0
|
4月前
|
JSON 缓存 NoSQL
redis序列化数据时,如何包含clsss类型信息?
通过配置 `com.fasterxml.jackson.databind.ObjectMapper` 的 `enableDefaultTyping` 方法,可以使序列化后的 JSON 包含类信息。
61 2