- Spring对于redis的集成很大程度上方便了开发人员操作redis的各种数据。屏蔽了对于底层redis连接的获取,释放。Spring-data-redis主要集成了jedis第三方客户端的操作。
redis中主要有5种数据类型,分别为String,List,Set,Map,sort set.每一种数据结构在spring中都封装成一个操作接口。类图如下:
上述5个接口定义了redis操作行为,主要是面向上层应用操作,程序开发人员直接通过这些接口的实例进行操作redis。每一种数据接口都有一个默认的实现类,都是以Defalut开头,同时都继承了AbstractOperations抽象类。该父类中定义了RedisTemplate的应用,同时通过定义一个内部类来实现对回调接口RedisCall的引用。核心源码如下:
abstract class AbstractOperations<K, V> {
// utility methods for the template internal methods
// 内部类实现回调接口
abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {
private Object key;
public ValueDeserializingRedisCallback(Object key) {
this.key = key;
}
// 采用模板方法设计,doInRedis中定义算法骨架,具体的子类实现inRedis功能
public final V doInRedis(RedisConnection connection) {
byte[] result = inRedis(rawKey(key), connection);
return deserializeValue(result);
}
// 具体子类实现inRedis功能,作为模板方法的具体实现功能部分。
protected abstract byte[] inRedis(byte[] rawKey, RedisConnection connection);
}
RedisTemplate<K, V> template;
}
- 抽象类AsbtractOperations中的抽象内部类ValueDeserializingRedisCallback 实现了回调接口RedisCall,该接口中定义了一个非常重要的方法就是doInRedis(RedisConnection connection)。这是spring-data-redis中非常核心的方法,后续会讲述。该抽象内部类ValueDeserializingRedisCallback 采用了模板方法思想,doInRedis方法为业务逻辑的主要执行入口,抽象方法inRedis,主要是具体的实现类去完成最终的redis操作。redis是一个Key-Value数据库,所以inRedis中方法参数都可以定义为key和RedisConnection两个参数。
- RedisConnection顾名思义,就是封装了redis连接,借鉴java.sql.connection思想,主要封装了底层操作redis的操作。
接口 RedisConnection继承了RedisCommand接口,而RedisCommand接口继承了一堆封装了redis操作命令的接口。RedisConnection是Redis操作的核心接口。
通过UML类图可以发现RedisConnection接口具体实现主要有JedisConnection和JedisClusterConnection两个实现类。分别封装了redis操作,redis哨兵和RedisCluster操作。
- RedisTemplate中最核心的设计思想主要是两部分,第一是采用ThreadLocal将redis连接与线程请求绑定,即每个线程都会操作单独的redis连接。第二是采用回调的设计思想,将回调接口RedisCallback中具体的redis操作行为最终采用匿名内部类实现。即实现上述中ValueDeserializingRedisCallback 类中的抽象方法InRedis。这两个部分的设计完美的体现了Spring封装redis的操作核心理念。
- TransactionSynchronizationManager事物同步资源管理器,这个工具类是spring事物模块中的一个核心操作类。在spring-tx.jar中,可见这个工具类的重要性。这个工具类中有如下几个属性:
// map存放资源的信息,以key-value形式作为一个整体存储在ThreadLocal中
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<String>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<Boolean>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<Integer>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<Boolean>("Actual transaction active");
- TransactionSynchronizationManager中的属性都是ThreadLocal类型变量,其中ThreadLocal中的存储对象类型有map息),String(当前事物名称)等。其中,事物资源又以map形式存储,即一个连接对应一个key。
- 该类中主要的方法有getResource(key)和bindResource(key,value)。这两个方法底层都是采用ThreadLocal进行存储。因为ThreadL储,数据结构为。对象类型的有map,set,String,boolean等。
- bindResource源码如下:
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
// 获取当前线程绑定的map对象,判断是否为空
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<Object, Object>();
resources.set(map);
}
// 将应用的key,value存放在map中。
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}
- 通过源码可以看出ThreadLocal类型的resources获取当前线程的变量副本对象map,将当前key,value以map的形式存储在threadLocal中。
- getResource(key)源码如下:
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
// 根据key真正获得资源的方法
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
其中,doGetResource(key)源码如下:
private static Object doGetResource(Object actualKey) {
// 1. 获取当前线程的map变量副本
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
//2. 根据key从map中获取value对象
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
其中,resources.get()是从ThreadLocal中获取当前线程绑定的map对象,map.get(actualKey)才是根据当前actualKey获取真正的应用资源 。从整个最底层来说,就是map中嵌套一个map数据结构。
资源管理器具体使用
- 在RedisTemplate具体的代码中,主要是提现如下方法中使用资源绑定类:
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
// 1. 正常redis操作,俩boolean的值都为false
Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(action, "Callback object must not be null");
// 2. 根据工厂获取RedisConnectionFactory
RedisConnectionFactory factory = getConnectionFactory();
RedisConnection conn = null;
try {
// 3. 事物支持,redis支持事物操作,没有事物则不执行此行代码
if (enableTransactionSupport) {
// only bind resources in case of potential transaction synchronization
conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
} else {
// 4. 获取redisConnection的工具类,获取redis连接的时候,就决定了是否支持事物,
//是否将factory与当前connection作为key-value绑定到threadLocal中。
conn = RedisConnectionUtils.getConnection(factory);
}
// 5. 检查当前获取的连接是否已经与当前线程绑定
boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
// 6. 不用管,默认返回当前连接
RedisConnection connToUse = preProcessConnection(conn, existingConnection);
// 7. 是否管道支持
boolean pipelineStatus = connToUse.isPipelined();
if (pipeline && !pipelineStatus) {
connToUse.openPipeline();
}
// 8 目前默认返回当前连接,spring没有什么实现
RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
/**
** 真正实现redis操作的代码,回调接口中具体实现在spring中是由不同的匿名内部类实
* 现的。这个也是精华所在
*/
T result = action.doInRedis(connToExpose);
// close pipeline
if (pipeline && !pipelineStatus) {
connToUse.closePipeline();
}
// TODO: any other connection processing?
return postProcessResult(result, connToUse, existingConnection);
} finally {
// 9 .关闭当前连接
RedisConnectionUtils.releaseConnection(conn, factory);
}
}
上述源码中,由于数据库连接属于局部变量,在获取过程已经判断是否进行资源绑定。对于普通的redis操作其实是没有必要的,但是对于redis的事物操作是非常有必要进行判断的。
接口回调使用
对于spring中操作redis来说,都是采用RedisTemplate调用的String,set,map等对应的方法来获取ValueOperations,SetOperations,MapOperations等类型的对象(一些公共的redis操作可以直接使用redisTemplate),然后再通过他们进行redis的操作。即RedsiTemplate是Spring中操作redis的入口。
// 获取valueOperations具体类型,然后进行具体的操作
public ValueOperations<K, V> opsForValue() {
if (valueOps == null) {
valueOps = new DefaultValueOperations<K, V>(this);
}
return valueOps;
}
// 获取ListOperations具体类型,然后进行具体的操作
public ListOperations<K, V> opsForList() {
if (listOps == null) {
listOps = new DefaultListOperations<K, V>(this);
}
return listOps;
}
RedisTemplate中接口回调内部实现主要体现在5中数据结构操作上。现在以DefaultValueOperations为例进行分析,该类中根据最开始的类图可以发现,它是继承AbstractOperations同时实现ValueOperations接口。同时这个父类中持有RedisTemplate的引用,同时还有一个抽象内部类实现了回调接口,可以认为是变异的实现回调接口。
abstract class AbstractOperations<K, V> {
// utility methods for the template internal methods
// 内部类实现了回调接口,可以认为变异的实现回调接口
abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {
private Object key;
public ValueDeserializingRedisCallback(Object key) {
this.key = key;
}
// 采用模板方法设计,具体的实现类去完成inRedis中的算法功能
public final V doInRedis(RedisConnection connection) {
byte[] result = inRedis(rawKey(key), connection);
return deserializeValue(result);
}
// 模板方法中的需要子类去完成的功能
protected abstract byte[] inRedis(byte[] rawKey, RedisConnection connection);
}
// 持有RedisTemplate的引用
RedisTemplate<K, V> template;
AbstractOperations(RedisTemplate<K, V> template) {
this.template = template;
}
}
DefaultValueOperations中的每一个redis的方法操作,都需要去调用RedisTemplate中的execute方法,而execute方法中的参数为RedisCallback类型,通常是传递一个该接口的具体实现类实例就可以。现在可以通过匿名内部类去实现回调接口RedisCallback,实现inRedis方法。通过上面分析可以知道,每个匿名内部类都是根据模板方法思想设计实现的。可以通过时序图分析一下,使用RedisTemplate流程:
上述时序图中,ValueOperation中定义了RedisCallback内部抽象实现类。
思想总结
RedisTemplate的设计上总结出了两个重要的设计理念。
第一:对于多线程的共享的资源,可以采用ThreadLocal进行线程副本的拷贝,即每一个请求线程都拥有自己独特的资源副本。这种以空间换时间的方式能够更好的容纳更高的并发请求。
第二:回调的运用。通过分析源码可以看出,回调接口声明的方法一般都是直接声明业务逻辑的方法,设计回调接口可以让其他类或对象去调用该接口声明的方法。回调接口可以具体的实现类或者采用匿名内部类去实现。采用匿名内部类实现类时,最大的好处就是减少大量的实现类代码,使整个代码看起来整洁,优雅。通常回调接口作为方法中的参数。