前提介绍
上一篇文章介绍了一下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方法保存/获取键值对。
- 使用key序列化器把key转换成二进制码
- 使用hash key序列化器把hashKey转换成二进制码
- lambda表达式实现RedisCallback接口,然后调用redistemplate的execute方法,根据key和hashKey获取value的二进制码
- 使用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; } 复制代码
保存
- 使用key序列化器把key转换成二进制码
- 使用hash key序列化器把hashKey转换成二进制码
- 使用hash value序列化器把value转换成二进制码
- 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来支持复杂类型