写点什么

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

发布于: 刚刚
👊【Spring技术原理系列】分析探究RedisTemplate的序列化和反序列化+泛型机制

前提介绍

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

Redis 序列化与反序列化

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

序列化

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

反序列化

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

RedisTemplate 的泛型

通常用法

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


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


如果 val 是 Integer


@ResourceRedisTemplate<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发布订单模式发布消息时,序列化channelprivate 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);
@Overridepublic <T> T execute(SessionCallback<T> session) { //**执行redis命令时会判断是否已经初始化,需调用afterPropertiesSet进行初始化 Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it"); ...}
复制代码

afterPropertiesSet 初始化序列化属性

@Overridepublic 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 保存
@Overridepublic 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 方法


@Overridepublic 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 方法,返回执行的结果。


@Nullablepublic <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 类型设置一个成员属性


@Overridepublic <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 中


@Overridepublic 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 的效果。


@ResourceRedisTemplate<String,Integer> tplInt;
@ResourceRedisTemplate<String,Person> tplPerson;
public void testGet(){ Integer x = tplInt.get("valOfX"); Person p = tplPerson.get("valOfPerson");}
复制代码


  • redis 返回的是 byte[] 类型,要用一个 serializer 做反序列化。

  • RedisTemplate 中的 serializer 得是一个 bean,即是一个实例化的对象。


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


  • StringRedisSerializer 实现的是 RedisSerializer<String>

  • JdkSerializationRedisSerializer 实现的是 RedisSerializer<Object>


后者为了兼容所有类型,所以设置为 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来支持复杂类型
复制代码


发布于: 刚刚阅读数: 3
用户头像

🏆 2021年InfoQ写作平台-签约作者 🏆 2020.03.25 加入

【个人简介】酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“ 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、APM专题及微服务/分布式体系等

评论

发布
暂无评论
👊【Spring技术原理系列】分析探究RedisTemplate的序列化和反序列化+泛型机制