zoukankan      html  css  js  c++  java
  • Spring Redis Data,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方法:

    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,如下面的代码:

    @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:

    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 种数据类型操作共享:

    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的构造方法如下:

    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)组成的,分数是用于排序的 一个双精度数字,这个接口要 求实现两个方法 :

    public interface TypedTuple<V> extends Comparable<TypedTuple<V>> {
    		V getValue();
    		Double getScore();
    	}

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

    5.RedisTemplate

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

    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为例:

    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 方法,他们参数不同,这里只列举一个:

    	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操作,比如下面的代码,在大部分情况下都会抛出异常 :

    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的方法,如下代码:

    //第二行代码是例子,下面是一步步调用的方法
    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互联网轻量级框架整合开发》 

  • 相关阅读:
    LootCode-链表排序-Java
    Vue创建项目及基本语法 一
    七大设计原则、单一职责原则
    SpringBoot 将自制的Starter 发布到远程公服
    SpringBoot 系列
    Mysql Sql 语句练习题 (50道)
    一、SpringBoot学习笔记_Eclipse 安装 SpringBoot、配置Gradle
    七、垃圾回收_判断对象是否存活算法_可达性分析法详解
    从零开始搭建Django+vue前后端分离项目
    Airtest一:批量运行脚本、汇总报告、导出报告
  • 原文地址:https://www.cnblogs.com/baichendongyang/p/13235494.html
Copyright © 2011-2022 走看看