【Spring技术原理】分析探究RedisTemplate的序列化和反序列化+泛型机制

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【Spring技术原理】分析探究RedisTemplate的序列化和反序列化+泛型机制

前提介绍


上一篇文章介绍了一下Java实现序列化的众多手段和优秀框架,现在我们针对于序列化和反序列化结合这些优秀的框架进行实现。


Redis序列化与反序列化


Redis底层以二进制/字符串形式存储内容;



序列化

把java对象转换为二进制/字符串,然后存储到内存中;


反序列化

读取内存中的二进制/字符串,然后转换为java对象;



RedisTemplate 的泛型


通常用法


RedisTemplate<String, String> 表示操作的 key 和 val 都是String类型。

@Resource
RedisTemplate<String, String> redisTemplate;
public void doTest(){
  redisTemplate.opsForValue().setIfAbset("key", "val", Duration.ofSecend(100));
}
复制代码


如果 val 是 Integer

@Resource
RedisTemplate<String, Integer> redisTemplate;
public void doTest(){
  redisTemplate.opsForValue().setIfAbset("key", 100, Duration.ofSecend(100));
}
复制代码


这个就可能报错了 这是因为springboot访问redis时的序列化操作。




Serializer序列化器


Springboot与Redis的交互是以二进制方式进行(byte[])。为了支持Java中的数据类型,就要对操作的对象(key,value,hashKey,hashValue...)做序列化操作。redisTemplate 只为 key value hashKey hashValue 设置serializer


Springboot提供了几个序列化


  • JdkSerializationRedisSerializer (默认)
  • StringRedisSerializer
  • 其他 或 自定义


JdkSerializationRedisSerializer
public byte[] serialize(@Nullable Object object)
复制代码


StringRedisSerializer


使用StringRedisSerializer 只支持 string类型,所以如果使用RedisTemplate<String, Integer>就会报错。

public byte[] serialize(@Nullable String string)
复制代码
  • 如果使用JdkSerializationRedisSerializer,则不仅支持 RedisTemplate<String, Integer>,同样能支持任意自定义类型 RedisTemplate<String, Person>。
  • 然而这种默认的序列化方式会导致redis中保存的key和value可读性较差,出现一些不可读的16进制字符



RedisTemplate序列化与反序列化实现


RedisSerializer序列化组件的相关属性
/**
 * 是否初始化,需调用afterPropertiesSet进行初始化,执行redis命令时会判断是否已经初
 * 始化
 */
private boolean initialized = false;
//启用默认的序列化
private boolean enableDefaultSerializer = true;
//默认的序列化器
private @Nullable RedisSerializer<?> defaultSerializer;
//key序列化器
@SuppressWarnings("rawtypes") 
private @Nullable RedisSerializer keySerializer = null;
//value序列化器
@SuppressWarnings("rawtypes") 
private @Nullable RedisSerializer valueSerializer = null;
//hash key序列化器,在hash类型的hash key和stream类型field中使用
@SuppressWarnings("rawtypes")
private @Nullable RedisSerializer hashKeySerializer = null;
//hash value序列化器,在hash类型value和stream类型value中使用
@SuppressWarnings("rawtypes") 
private @Nullable RedisSerializer hashValueSerializer = null;
//字符串序列化器,在redis发布订单模式发布消息时,序列化channel
private RedisSerializer<String> stringSerializer = RedisSerializer.string();
//操作string类型
private final ValueOperations<K, V> valueOps = new DefaultValueOperations<>(this);
//操作list类型
private final ListOperations<K, V> listOps = new DefaultListOperations<>(this);
//操作set类型
private final SetOperations<K, V> setOps = new DefaultSetOperations<>(this);
//操作stream流类型
private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations<>(this, new ObjectHashMapper());
//操作zset类型
private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations<>(this);
//操作geo地理位置类型
private final GeoOperations<K, V> geoOps = new DefaultGeoOperations<>(this);
//操作hyperLogLog类型
private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations<>(this);
//操作cluster集群
private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations<>(this);
@Override
public <T> T execute(SessionCallback<T> session) {
    //**执行redis命令时会判断是否已经初始化,需调用afterPropertiesSet进行初始化
  Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
  ...
}
复制代码



afterPropertiesSet初始化序列化属性

@Override
public void afterPropertiesSet() {
    //**调用父类的afterPropertiesSet方法,判断是否初始化redis连接工厂
  super.afterPropertiesSet();
  boolean defaultUsed = false;
    //**如果没的设置默认的序列化器,则使用jdk序列化器为默认的序列化器,可调用setDefaultSerializer设置默认的序列化器
  if (defaultSerializer == null) {
    defaultSerializer = new JdkSerializationRedisSerializer(
        classLoader != null ? classLoader : this.getClass().getClassLoader());
  }
  if (enableDefaultSerializer) {
        //**如果启用默认的序列化,且没有设置key序列化器,则使用默认的序列化器为key的序列化器
    if (keySerializer == null) {
      keySerializer = defaultSerializer;
      defaultUsed = true;
    }
    //**如果启用默认的序列化,且没有设置value序列化器,则使用默认的序列化器为value的序列化器
    if (valueSerializer == null) {
      valueSerializer = defaultSerializer;
      defaultUsed = true;
    }
    //**如果启用默认的序列化,且没有设置key序列化器,则使用默认的序列化器为hash key的序列化器
    if (hashKeySerializer == null) {
      hashKeySerializer = defaultSerializer;
      defaultUsed = true;
    }
    //**如果启用默认的序列化,且没有设置value序列化器,则使用默认的序列化器为hash value的序列化器
    if (hashValueSerializer == null) {
      hashValueSerializer = defaultSerializer;
      defaultUsed = true;
    }
  }
    //**启用默认的序列化,则默认的序列化器不能不空
  if (enableDefaultSerializer && defaultUsed) {
    Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
  }
    //**如果没有设置redis脚本执行器,则设置redis脚本执行器为DefaultScriptExecutor
  if (scriptExecutor == null) {
    this.scriptExecutor = new DefaultScriptExecutor<>(this);
  }
    //**设置已初始化
  initialized = true;
}
//**设置默认的序列化器
public void setDefaultSerializer(RedisSerializer<?> serializer) {
  this.defaultSerializer = serializer;
}
复制代码



RedisTemplate使用


  • 执行写入,先调用对应的序列化器,把key/value序列化为二进制码,在保存到redis中。
  • 执行读取,先根据key获取value的二进制码,调用value序列化器反序化为java对象



string类型的序列化和反序列化


首先调用DefaultValueOperations的set/get方法保存/获取键值对,创建匿名内部类实现ValueDeserializingRedisCallback,然后调用redistemplate的execute方法


rawKey序列化键


使用key序列化器把key转换成二进制码

@SuppressWarnings("unchecked")
private byte[] rawKey(Object key) {
  Assert.notNull(key, "non null key required");
  if (keySerializer == null && key instanceof byte[]) {
    return (byte[]) key;
  }
  return keySerializer.serialize(key);
}
复制代码


rawValue序列化键


使用value序列化器把value转换成二进制码

@SuppressWarnings("unchecked")
private byte[] rawValue(Object value) {
  if (valueSerializer == null  && value instanceof byte[]) {
    return (byte[]) value;
  }
  return valueSerializer.serialize(value);
}
复制代码



Set保存
@Override
public void set(K key, V value) {
    //**使用value序列化器把value转换成二进制码
  byte[] rawValue = rawValue(value);
  execute(new ValueDeserializingRedisCallback(key) {
        //**把序列化后的key和value的二进制码保存到redis中
    @Override
    protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
      connection.set(rawKey, rawValue);
      return null;
    }
  }, true);
}
复制代码



get获取


创建匿名内部类实现ValueDeserializingRedisCallback,然后调用redistemplate的execute方法

@Override
public V get(Object key) {
  return execute(new ValueDeserializingRedisCallback(key) {
        //把根据序列化后的key获和value的二进制码
    @Override
    protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
      return connection.get(rawKey);
    }
  }, true);
}
复制代码



execute执行方法


Redistemplate的execute方法调用ValueDeserializingRedisCallback的doInRedis方法,调用ValueDeserializingRedisCallback的doInRedis方法,返回执行的结果。

@Nullable
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
    ...
    T result = action.doInRedis(connToExpose);
    ...
}
复制代码



inRedis执行方法


ValueDeserializingRedisCallback.doInRedis方法调用inRedis方法,调用inRedis方法,把序列化后的key和value的二进制码保存到redis中(set方法会返回null),使用value序列化器把二进制的value反序列化为java对象。

public final V doInRedis(RedisConnection connection) {
  byte[] result = inRedis(rawKey(key), connection);
  //使用value序列化器把二进制的value反序列化为java对象
  return deserializeValue(result);
}
@SuppressWarnings("unchecked")
V deserializeValue(byte[] value) {
  if (valueSerializer() == null) {
    return (V) value;
  }
  return (V) valueSerializer().deserialize(value);
}
复制代码



hash类型的序列化和反序列化


Redistemplate没有为hash类型设置一个成员属性

@Override
public <HK, HV> HashOperations<K, HK, HV> opsForHash() {
  return new DefaultHashOperations<>(this);
}
复制代码



获取


首先调用DefaultHashOperations的put/get方法保存/获取键值对。


  1. 使用key序列化器把key转换成二进制码
  2. 使用hash key序列化器把hashKey转换成二进制码
  3. lambda表达式实现RedisCallback接口,然后调用redistemplate的execute方法,根据key和hashKey获取value的二进制码
  4. 使用hash value序列化器把二进制码的value反序列化为java对象
@Override
@SuppressWarnings("unchecked")
public HV get(K key, Object hashKey) {
  byte[] rawKey = rawKey(key);
  byte[] rawHashKey = rawHashKey(hashKey);
  byte[] rawHashValue = execute(connection -> connection.hGet(rawKey, rawHashKey), true);
  return (HV) rawHashValue != null ? deserializeHashValue(rawHashValue) : null;
}
复制代码



保存


  1. 使用key序列化器把key转换成二进制码
  2. 使用hash key序列化器把hashKey转换成二进制码
  3. 使用hash value序列化器把value转换成二进制码
  4. lambda表达式实现RedisCallback接口,然后调用redisTemplate的execute方法,把序列化后的key、hashKey和value的二进制码保存到redis中
@Override
public void put(K key, HK hashKey, HV value) {
  byte[] rawKey = rawKey(key);
  byte[] rawHashKey = rawHashKey(hashKey);
  byte[] rawHashValue = rawHashValue(value);
  execute(connection -> {
    connection.hSet(rawKey, rawHashKey, rawHashValue);
    return null;
  }, true);
}
复制代码


使用hash key序列化器把hashKey转换成二进制码

@SuppressWarnings("unchecked")
<HK> byte[] rawHashKey(HK hashKey) {
  Assert.notNull(hashKey, "non null hash key required");
  if (hashKeySerializer() == null && hashKey instanceof byte[]) {
    return (byte[]) hashKey;
  }
  return hashKeySerializer().serialize(hashKey);
}
复制代码


使用hash value序列化器把value转换成二进制码

@SuppressWarnings("unchecked")
<HV> byte[] rawHashValue(HV value) {
  if (hashValueSerializer() == null && value instanceof byte[]) {
    return (byte[]) value;
  }
  return hashValueSerializer().serialize(value);
}
复制代码


使用hash value序列化器把二进制码的value反序列化为java对象

@SuppressWarnings("unchecked")
<HV> HV deserializeHashValue(byte[] value) {
  if (hashValueSerializer() == null) {
    return (HV) value;
  }
  return (HV) hashValueSerializer().deserialize(value);
}
复制代码



Redistemplate的execute方法调用的RedisCallback接口doInRedis方法



自定义序列化


  • JdkSerializationRedisSerializer虽然在redis中保存的数据是不可读的,但是操作起来很方便,可直接指定返回值的类型,免去了再次转换之繁琐。其实现原理是在redis中存储的数据里包含着数据类型。


  • 在redis中数据不保存类型信息,通过为template指定value的类型,获取期望类型值。使用StringRedisSerializer,但能达到JdkSerializationRedisSerializer的效果。
@Resource
RedisTemplate<String,Integer> tplInt;
@Resource
RedisTemplate<String,Person> tplPerson;
public void testGet(){
  Integer x = tplInt.get("valOfX");
  Person p = tplPerson.get("valOfPerson");
}
复制代码
  • redis返回的是 byte[] 类型,要用一个 serializer 做反序列化。
  • RedisTemplate中的 serializer 得是一个bean,即是一个实例化的对象。


这个对象要实现 RedisSerializer 接口,必定要绑定在一个固定类型上,如果是String就不能是Integer。所以无法根据需要传入。



  • StringRedisSerializer实现的是 RedisSerializer
  • JdkSerializationRedisSerializer 实现的是 RedisSerializer
    后者为了兼容所有类型,所以设置为Object,反序列化后的数据是一个Object,这样就丢掉了原本的所有信息。所以如果要返回外部需要的类型,只能在序列化后做一次值的类型转换。本质逻辑类似如下:
public Object get(String key){
  //get data from redis
  return (Object)(new Person())
}
Person p = (Person)get();
复制代码



  • springboot的序列化可以自定义,自己搭配。比如
//默认改成 StringRedisSerializer,key类的原样
template.setDefaultSerializer(new StringRedisSerializer());
//为value支持复杂类型
JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
template.setValueSerializer(jdkSerializer);
template.afterPropertiesSet();
这样 key、hashKey、hashValue可读的。value来支持复杂类型




相关实践学习
基于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
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
103 2
|
27天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
84 14
|
1月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
81 4
|
2月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
85 8
|
2月前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
2月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
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序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。
|
2月前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
63 0