文章目录

  • 概述
  • 序列化器
  • 作用和原理
  • JDK 序列化方式
  • 多一点
  • String 序列化方式
  • JSON 序列化方式
  • 总结
  • 源码


概述

在SpringBoot中使用redis基本上都是通过Spring Data Redis,那就不得不说RedisTemplate了。在我刚接触它的时候比较懵逼的就是给其设置各种序列化器了,今天我们来唠唠他们。

序列化器

众所周知,使用RedisTemplate可以对Redis的各种数据结构进行操作,如下图所示。

redistemplate怎么用泛型 redistemplate multi_redis

作用和原理

那我们为什么需要序列化器呢,这是个啥玩意儿?

现在闭目思考一下我们是如何使用redis的?是不是先将数据存储在redis上,然后用的时候再读取出来?

那我们存储在redis里的内容是啥呢?有时是字符串,例如"ShuSheng007",大部分时间是对象,例如StudentList<Student>Map<String,Student>等等。这些个对象肯定是不能直接存储到redis上的,我们需要想办法先把它们转成byte[]后才能存储到redis上,这就是所谓的序列化。等用的时候还的把byte[]转化为相应的对象,这就是所谓的反序列化。序列化器就是完成这两个功能的。

下面是Spring中Redis序列化器的接口,从源码中可以非常清晰的看到它就干了这两个事情。

public interface RedisSerializer<T> {

	@Nullable
	byte[] serialize(@Nullable T t) throws SerializationException;

	@Nullable
	T deserialize(@Nullable byte[] bytes) throws SerializationException;
}

之所以存在这么多序列化器,是因为我们可以通过各种方式将对象转为byte[],也就是这个接口可以有各种实现类。总结一下,大概有如下几种:

  • JDK 序列化方式 (默认)
  • String 序列化方式
  • JSON 序列化方式
  • XML 序列化方式

其中XML序列化器不怎么用,我们就忽略它吧。

在讲解各种序列化器之前我们应该先搞清楚RedisTemplate都有哪些地方需要设置序列化器。

public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {

	@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;
	
}

从上面的源码可以发现总共有四个地方需要设置序列化器。这个和Redis提供的数据结构有关,所以我们在往redis里面存放数据的时候有两种方式。一种是key/value形式,另一种就是 key/hashMap, 那个hashMap又是key/value形式, 所以一共有四个地方用到序列化器。

key和hashKey一般都使用String序列化方式,有变化的一般是value和hashValue。

JDK 序列化方式

JdkSerializationRedisSerializer完成,它是默认序列化器,如果不手动设置就会使用它。它是利用Java自身提供的序列化方案来工作的,也就是说你要存放到redis中的数据必须是可序列化的,例如实现了Serializable接口。

我们来实践一下,使用下面的代码就给RedisTemplate的value设置JdkSerializationRedisSerializer

template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.java());

我们有如下一个类

public class KeyValue implements Serializable {
    private String key;
    private Object value;
    ...
}

将其保存到redis中

public void testRedisSerializer() {
    ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    valueOperations.set("object",new KeyValue("age",18));
}

查看结果,你会发现value是人类不可读的样子

\xac\xed\x00\x05sr\x00.top.shusheng007.redisintegrate.domain.KeyValue\xb2\xf9\xec\xa5\xb5\x89\xb99\x02\x00\x02L\x00\x03keyt\x00\x12Ljava/lang/String;L\x00\x05valuet\x00\x12Ljava/lang/Object;xpt\x00\x03agesr\x00\x11java.lang.Integer...

正是因为这个原因,外加跨平台问题导致其在生产中用的也比较少。

多一点

如果在创建JdkSerializationRedisSerializer时传入一个ClassLoader,如果这个类加载器和你使用类的加载器是同一个的话,查询出的值可以直接转成对应的类型对象的。

//传入当前配置类的ClassLoader
template.setValueSerializer(RedisSerializer.java(getClass().getClassLoader()));

valueOperations.set("object",new KeyValue("age",18));
//从redis获取的值可以直接强转
KeyValue keyValue = (KeyValue) valueOperations.get("object");

String 序列化方式

StringRedisSerializer完成,这个应该没什么可说的。一般情况下,key和hashKey都使用这个序列化器,它们两一般也就存放字符串。value和hashValue一般都不会设置为它,Spring Data Redis 单独提供了一个操作字符串的StringRedisTemplate

public class StringRedisTemplate extends RedisTemplate<String, String> {
	...
}

JSON 序列化方式

GenericJackson2JsonRedisSerializer或者Jackson2JsonRedisSerializer完成。我们一般将value和hashValue的序列化器设置为其中一个,用来以json的形式保存数据。那这两个有什么区别呢?

  • Jackson2JsonRedisSerializer

设置序列化器

Jackson2JsonRedisSerializer<Object> valuesSerializer2 = new Jackson2JsonRedisSerializer(Object.class);
template.setValueSerializer(valuesSerializer2);

使用

valueOperations.set("object",new KeyValue("age",18));

 Object keyValue =  valueOperations.get("object");
 
 String clsName = keyValue.getClass().getCanonicalName();
 log.info("object:{},name:{}", keyValue,clsName);

查看redis中value值,可见就是普通json格式

{
	"key": "age",
	"value": 18
}

其中那个keyValue是一个java.util.LinkedHashMap类型,这个是java自动解析的类型。所以如果我们要将其解析成对应的类型的话就要借助与Jackson了。

ObjectMapper objectMapper = new ObjectMapper();
 KeyValue keyValue = objectMapper.convertValue(keyValue, KeyValue.class);
  • GenericJackson2JsonRedisSerializer

这个与Jackson2JsonRedisSerializer的区别是生成的json里携带了类信息,反序列化为同一个类的时候不需要借助于Jackson。但这也会产生新的问题,例如一个服务向redis里写数据,另一个服务取数据,但是他们的类信息不一样就无法反序列化了。

设置序列化器

Jackson2JsonRedisSerializer<Object> valuesSerializer2 = new Jackson2JsonRedisSerializer(Object.class);
template.setValueSerializer(valueSerializer);

使用,注意我们在反序列化的时候直接进行了强转。

valueOperations.set("object",new KeyValue("age",18));

 KeyValue keyValue = (KeyValue) valueOperations.get("object");

 String clsName = keyValue.getClass().getCanonicalName();
 log.info("object:{},name:{}", keyValue,clsName);

查看redis中value值,可见多了一个@class信息,是在反序列时提供类型信息的。

{
	"@class": "top.shusheng007.redisintegrate.domain.KeyValue",
	"key": "age",
	"value": 18
}

总结

通常key/hashKey使用字符串序列化器,value/hashValue使用Json序列化器。当服务自己存自己取的话使用GenericJackson2JsonRedisSerializer,但是如果取其他服务存储的Json值就要使用Jackson2JsonRedisSerializer

以上就是RedisRemplate的几个序列化器的总结。是不是感觉也没那么难了?小白面前的大山就是老鸟眼中的土坷垃。你回头看看你的经历,是不是很多当时感觉非常艰难的事情在你攻克他们后感觉也没那么难,而且还会感慨一句:这也没多难,为什么我当时就是不会呢?人生也是一样,当你回顾你的一生时大多遗憾的事情都是没有做的事情…

源码

一如既往,你可以从首发找到源码 redis-integration