我是靠谱客的博主 超帅小海豚,这篇文章主要介绍Spring Redis Data,RedisTemplate原理1.获取一个RedisTemplate对象,现在分享给大家,希望可以做个参考。

1.获取一个RedisTemplate对象

一般都是通过连接池连接Redis的,要在spring中使用Redis,首先,我们需要配置JedisPoolConfig对象,如下图:

在使用 Spring提供的 RedisTemplate之前需要配置 Spring所提供的连接工厂,在 Spring Data Redis 方案中它提供了 4 种工厂模型:

JredisConnectionFactory。JedisConnectionFactory。 LettuceConnectionFactory。SrpConnectionFactory

如果想换成其他的连接工厂,修改bean标签里面的class属性为对应的工厂模型类即可。

普通的连接使用没有办法把 Java对象直接存入 Redis,而需要我们自己提供方案,这时往往就是将对象序列化,然后使用 Redis 进行存储,而取回序列化的内容后,在通过转换转变为 Java 对象, Spring 模板中提供了封装的方案,在它内部提供了 RedisSerializer 接口(org.spring企amework.data.redis.serializer.RedisSerializer)和一些实现类,如下图:

下面几种方法实现了RedisSerializer接口:

  • GenericJackson2JsonRedisSerializer,通用的使用 Json2.jar 的包,将 Redis 对象的序 列化器。

  • Jackson2JsonRedisSerializer<T>,通过 Jackson2.jar包提供的序列化进行转换。

  • JdkSerializationRedisSerializer<T>, 使用 JDK 的序列化器进行转化。

  • OxmSerializer, 使用 SpringO/X 对象 Object和 XML相互转换。

  • StringRedisSerializer, 使用字符串进行序列化 。

  • GenericToStringSerializer,通过通用 的字符串序列化进行相互转换。

    使用它们就能够帮助我们把对象通过序列化存储到 Redis 中,也可以把 Redis存储的内容转换为 Java对象,为此 Spring提供的 RedisTemplate还有两个属性。

    • keySerializer 一键序列器 。 • valueSerializer 一值序列器。 这样就配置了 一个 RedisTemplate 的对象,并且 spring data redis 知道会用对应 的序列化器去转换 Redis 的键值。

2.Spring 对 Redis API 的基本封装

Java有多种 Redis 的 API,如:Jedis Jredis、Lettuce 等。为了融合这些不同的 APL Spring 给出 一个对底层操作的接口 RedisConnection,通过这个接口就消除了各种连接 API的差异,提供统一的接口规范来简化操作,如图:

Spring 对 Java 多种 Redis 连接 API 进行封装,而各个连接的实现类都继承抽象类AbstractRedisConnection,而这个抽象类实现了 RedisConnection 接口。所以对于使用者而言,只需要知道 RedisConnection 接口的 API 就可以消除各个 API 的差异了。

Spring 会提供创建这个接口对象的工厂----RedisConnectionFactory。其实就是上面第一点的创建工厂连接,如图:

3.Spring 对 Redis 命令的封装

Spring对Redis的6种数据类型的命令进行了更深层次封装,封装为6个接口:

​ • ValueOperations 一键值对操作接口

• HashOperations 一哈希操作接口

 • ListOperations一一链表操作接口

• SetOperations 一一无序集合操作接口

• ZSetOperations一一有序集合操作接口

• HyperLogLogOperations一一基数操作接口 。

Spring也会为它们提供默认的实现类, 在大部分情况下只要使用Spring提供的实现类即可,这些实现类是: DefaultValueOperations、DefaultHashOperations、DefaultListOperations、 DefaultSetOperations、 DefaultZSetOperations和 DefaultHyperLogLogOperations。 通过名字即可知道这些实现类对应的操作命令是什么。

如上图,只是列举了键值对和哈希这两种数据类型的命令。

下面我们来看看spring是怎么保存数据的,来看DefaultValueOperations类的 set方法:

复制代码
1
2
3
4
5
6
7
8
9
public void set(K key, V value) { final byte[] rawValue = rawValue(value); execute(new ValueDeserializingRedisCallback(key) { protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { connection.set(rawKey, rawValue); return null; } }, true); }

由上面的代码可以知道,Spring把 key和 value转化为 rawKey和 rawValue,然后通过 RedisConnection的set方法发给 Redis 存储。其实就是将key和value转换为字节,由Spring 通过 AbstractOperations 抽象类进行转化,它有两个方法 rawKey 和 rawValue,如下面的代码:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SuppressWarnings("unchecked") byte[] rawKey(Object key) { Assert.notNull(key, "non null key required"); if (keySerializer() == null && key instanceof byte[]) { return (byte[]) key; } return keySerializer().serialize(key); } ... @SuppressWarnings("unchecked") byte[] rawValue(Object value) { if (valueSerializer() == null && value instanceof byte[]) { return (byte[]) value; } return valueSerializer().serialize(value); }

Spring使用了 keySerializer和 valueSerializer两个序列化器对键值对进行序列化,这个序列化就把键值对转化为了二进制数组(byte[]),通过 RedisConnection传达给 Redis服务器。

对于这个序列化 Spring 也提供了接口RedisSerializer:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface RedisSerializer<T> { /** * Serialize the given object to binary data. * @param t object to serialize * @return the equivalent binary data */ byte[] serialize(T t) throws SerializationException; /** * Deserialize an object from the given binary data. * @param bytes object binary representation * @return the equivalent object instance */ T deserialize(byte[] bytes) throws SerializationException; }

        这个接口只有两个方法, 第一个方法就是是将对象(这个对象要求实现 Serializable接口)通过序列化转化为二进制数组;第二个方法是将已经序列化的二进制数据转化为对应的对象。只要实现了这个接口,并实现这两个方法,就可以自定义序列化器了,然后通过配置就能在 Spring 的上下文中使用自定义的序列化器。其实这个就是上面第一点提到的序列化与反序列化。

4.Spring对 Redis操作的封装

​ 在 Spring 中提供了对应的操作接口BoundKeyOperations,这是一个公共接口,它的方法可以给 6 种数据类型操作共享:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public interface BoundKeyOperations<K> { /** * Returns the key associated with this entity. * @return key associated with the implementing entity */ K getKey(); /** * Returns the associated Redis type. * @return key type */ DataType getType(); /** * Returns the expiration of this key. * @return expiration value (in seconds) */ Long getExpire(); /** * Sets the key time-to-live/expiration. * @param timeout expiration value * @param unit expiration unit * @return true if expiration was set, false otherwise */ Boolean expire(long timeout, TimeUnit unit); /** * Sets the key time-to-live/expiration. * @param date expiration date * @return true if expiration was set, false otherwise */ Boolean expireAt(Date date); /** * Removes the expiration (if any) of the key. * @return true if expiration was removed, false otherwise */ Boolean persist(); /** * Renames the key. <br> * <b>Note:</b> The new name for empty collections will be propagated on add of first element. * @param newKey new key */ void rename(K newKey); }

这是一个最基础的接口,为了实现各个数据类型不同的操作,在此接口上 Spring扩展出了其他的接口:

  • BoundValueOperations一一对于键值对的操作,这是 Redis 最基础的数据类型。

  • BoundHashOperations一一对于哈希数据的操作。

  • BoundListOperations一一对于链表( List)数据的操作。

  • BoundSetOperations一一对于集合( Set)数据的操作。

  • BoundZSetOperations一一对于有序集合( ZSet)数据的操作。

  • HyperLogLogOperations一一基数统计统计操作。

这样 Spring通过操作就可以把各个数据类型的命令封装到各个操作里面,提供统一的操作接口给调用者使用。 Spring 也对它们提供对应的默认实现类。这里只探讨BoundValueOperations接口的实现类DefaultBoundValueOperations来了解 Spring 对 Redis 操作的封装了。

DefaultBoundValueOperations的构造方法如下:

复制代码
1
2
3
4
public DefaultBoundValueOperations(K key, RedisOperations<K, V> operations) { super(key, operations); this.ops = operations.opsForValue(); }

key 的传递限制了这个类在操作对应的 Redis 的键值对,而属性 ops 是一个操作operations.opsForValue()是获取一个 ValueOperations 接口对象,可见它最终还是通过对命令 的封装进行操作的。这样它就能够操作命令封装的类对 Redis 的命令操作了。

注意, Spring 提供了一个接口TypedTuple 来操作有序集合,因为有序集合的元素 是由分数( score)和值( value)组成的,分数是用于排序的 一个双精度数字,这个接口要 求实现两个方法 :

复制代码
1
2
3
4
public interface TypedTuple<V> extends Comparable<TypedTuple<V>> { V getValue(); Double getScore(); }

getValue 方法是获取值 ,而 getScore 方法是返回一个分数,用于排序 。由于它继承了Comparable接口,所以还需要实现一个 compareTo 方法。

5.RedisTemplate

Spring 也为 Redis 提供了 一个 RedisTemlate 模板,通过它就可以快速操作 Redis。 在这个类里面定义了很多属性,如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware { private boolean enableTransactionSupport = false; //是否支持事务 private boolean exposeConnection = false; //是否暴露连接,用于代理访问的形式时 private boolean initialized = false; //初始化状态 private boolean enableDefaultSerializer = true; //默认的序列化器是否开启 private RedisSerializer<?> defaultSerializer; //默认序列化器 private ClassLoader classLoader; //类加载器 private RedisSerializer keySerializer = null; //键序列化器 private RedisSerializer valueSerializer = null; //值序列化器 private RedisSerializer hashKeySerializer = null; //hash数据结构键序列化器 private RedisSerializer hashValueSerializer = null; //hash数据结构值序列化器 //字符串序列化器 private RedisSerializer<String> stringSerializer = new StringRedisSerializer(); private ScriptExecutor<K> scriptExecutor;//Lua 脚本执行器 // cache singleton objects (where possible) private ValueOperations<K, V> valueOps; //字符串操作 private ListOperations<K, V> listOps; //链表操作 private SetOperations<K, V> setOps; //集合操作 private ZSetOperations<K, V> zSetOps; //有序集合操作 private HyperLogLogOperations<K, V> hllOps; //HyperLogLog 操作

通过上面的代码我们知道,可以通过设置 key和 value 的序列化器去控制其序列化,其次也可以获得各种操作来执行各种 Redis 的命令。还有一 个scriptExecutor来支持 Lua脚本的执行。

RedisTemplate 主要有 3 大类的操作 :

  • 数据类型的公共命令,比如 expire、 delete、 watch、 unwatch 等命令。

  • 获取对应的操作类,比如 ValueOperations 对象。

  • 执行多个命令或者其他用户回调模板。

5.1 RedisTemplate 公共命令

先来看看公共命令spring的封装,以删除键值对的delete为例:

复制代码
1
2
3
4
5
6
7
8
9
public void delete(K key) { final byte[] rawKey = rawKey(key); execute(new RedisCallback<Object>() { public Object doInRedis(RedisConnection connection) { connection.del(rawKey); return null; } }, true); }

这里面重点主要是execute方法,参数是RedisCallback接口,这个接口里面只有doInRedis这个方法,这里用的是匿名内部类的形式。在 RedisCallback 接口对象里,它采用了自己封装的 RedisConnection 接口操作 Redis 命令 。在 RedisTemplate 中, 有很多个execute 方法,他们参数不同,这里只列举一个:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) { Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it"); Assert.notNull(action, "Callback object must not be null"); //获取 Redis 连接池 RedisConnectionFactory factory = getConnectionFactory(); RedisConnection conn = null; try { //获取连接,判断是否存在/支持事务 if (enableTransactionSupport) { // only bind resources in case of potential transaction synchronization conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport); } else { conn = RedisConnectionUtils.getConnection(factory); } boolean existingConnection = TransactionSynchronizationManager.hasResource(factory); RedisConnection connToUse = preProcessConnection(conn, existingConnection); boolean pipelineStatus = connToUse.isPipelined(); //是否采用流水线 if (pipeline && !pipelineStatus) { connToUse.openPipeline(); } //是否采用代理访问 RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse)); //调用 RedisCallback 接口 的 doinRedis 方法 T result = action.doInRedis(connToExpose); // close pipeline if (pipeline && !pipelineStatus) { connToUse.closePipeline(); } // TODO: any other connection processing?即事后方法 return postProcessResult(result, connToUse, existingConnection); } finally { //是否支持事务,关闭事务 if (!enableTransactionSupport) { RedisConnectionUtils.releaseConnection(conn, factory); } } }

当用一个公共命令的时候, Spring会从 Redis 的连接池里获取连接,所以在一个方法里面使用 RedisTemplate操作,比如下面的代码,在大部分情况下都会抛出异常 :

复制代码
1
2
redisTemplate .multi(); redisTemlate.exec();

这是因为使用公共命令,每次 RedisTemplate 都会尝试在连接池里面拿到一条空闲的连接,而 redisTemplate.multi()和 redisTemlate.exec();执行的时候,在大部分的情况下都不是同一条连接,因此会在 redisTemlate.exec();执行过程中发生异常,因为它内部的 Redis 连接没有执行事务的开启(该连接在此之前没有执行 multi命令)。

5.2 获取操作类

RedisTemplate 的属性定义了 Redis 的 6种数据结构的操作类,只要通过opsForXXX这样的方法就可以得到各种命令的封装类。比如要对字符串操作,就可以使用:  redisTemplate.opsForValue(); //得到操作字符串的封装类

得到命令封装类如果要对某个键值对操作,那么也可以通过 boundXXXOps 来获取操作类,比如对字符串的 boundValueOps 方法,这样就可以得到一个字符串的操作类。它们的底层也是通过使用 RedisTemplate 的 execute方法去执行的,所以对于它们的操作还是类似 RedisTemplate 那样,执行一次命令就尝试在连接池里获取新的连接。 即其实通过 boundXXXOps 来获取操作类,最终还是调用xxxOperations的方法,如下代码:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//第二行代码是例子,下面是一步步调用的方法 redisTemplate.boundValueOps("name").set("baichen"); public BoundValueOperations<K, V> boundValueOps(K key) { return new DefaultBoundValueOperations<K, V>(key, this); } public DefaultBoundValueOperations(K key, RedisOperations<K, V> operations) { super(key, operations); this.ops = operations.opsForValue(); } public ValueOperations<K, V> opsForValue() { if (valueOps == null) { valueOps = new DefaultValueOperations<K, V>(this); } return valueOps; } private ValueOperations<K, V> valueOps;

第一行代码的执行过程是:先通过boundValueOps获取字符串的操作类,然后这个操作类会再调用ValueOperations这个类,然后这个类又返回RedisTemplate里面定义好的一些单例模式的操作类,然后这些类里面封装了相应的方法,如上面说的,最后都是调用DefaultValueOperations的方法。

 

 

具体的操作实例参见:https://mp.csdn.net/postedit/83745680

参考:《Java EE互联网轻量级框架整合开发》 

最后

以上就是超帅小海豚最近收集整理的关于Spring Redis Data,RedisTemplate原理1.获取一个RedisTemplate对象的全部内容,更多相关Spring内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(104)

评论列表共有 0 条评论

立即
投稿
返回
顶部