深入浅出Spring原理及实战「开发实战系列」分析探究RedisTemplate的序列化和反序列化+泛型机制

Posted 洛神灬殇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入浅出Spring原理及实战「开发实战系列」分析探究RedisTemplate的序列化和反序列化+泛型机制相关的知识,希望对你有一定的参考价值。

前提介绍

【【深入浅出Spring原理及实战】「开发实战系列」采用protostuff和kryo高性能序列化框架实现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来支持复杂类型

以上是关于深入浅出Spring原理及实战「开发实战系列」分析探究RedisTemplate的序列化和反序列化+泛型机制的主要内容,如果未能解决你的问题,请参考以下文章

深入浅出Spring原理及实战「开发实战系列」Spring-Cache扩展自定义(注解失效时间+主动刷新缓存)

深入浅出Spring原理及实战「开发实战系列」SpringSecurity技术实战之通过注解表达式控制方法权限

深入浅出Spring原理及实战「开发实战系列」OAuth2的技术体系架构和开发概览

深入浅出Spring原理及实战「开发实战系列」重新回顾一下异常重试框架Spring Retry的功能指南和实战

深入浅出Spring原理及实战「开发实战系列」Aspectj和LoadTimeWeaving的动态代理技术实现指南

深入浅出Spring原理及实战「开发实战系列」SpringSecurity与JWT实现权限管控以及登录认证指南