我们首先来看下面一段代码,这段代码是我们使用Jedis封装服务的一个实现:
@Service
public class JedisSpringDemo {
@Resource(name = "shardedJedisPool")
private ShardedJedisPool shardedJedisPool;
public String set(String key, String value){
ShardedJedis shardedJedis = null;
try{
// 从连接池中获取jedis分片对象
shardedJedis = shardedJedisPool.getResource();
// 设置值到redis中
return shardedJedis.set(key, value);
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
if(null != shardedJedis){
shardedJedis.close();
}
}
return null;
}
}
从上面的代码中,不知道大家有没有看出什么问题出来?就我看来,上面的这段代码违反了
DRY
原则,怎么说了,上面代码中的
try
,
catch
,
finally
中的绝大部分代码都是雷同的,唯一不同的就是我们
return
的那一行具体的调用方法,如果像这种方法很多的话
(jedis
提供了几十种类似的方法
)
,我们的代码重复率是很高的,代码重复率一旦高起来,相应的维护成本也会提高,下面我们就来引进一种改进方法
--
回调机制。
首先,我们创建一个接口类,该接口定义Jedis的操作,代码如下:
public interface RedisOperations {
<T> T execute(ConnectionCallback<T> action);
String set(final String key, final String value);
String get(final String key);
}
其次,定义连接
Redis
服务器的回调接口,代码如下:
public interface ConnectionCallback<T> {
T doInRedis(ShardedJedis shardedJedis);
}
最后定义具体的操作服务类,代码如下:
@Service("redisTemplate")
public class RedisTemplate implements RedisOperations{
@Resource(name = "shardedJedisPool")
private ShardedJedisPool shardedJedisPool;
@Override
public <T> T execute(ConnectionCallback<T> action) {
ShardedJedis shardedJedis = null;
try{
// 从连接池中获取jedis分片对象
shardedJedis = shardedJedisPool.getResource();
return action.doInRedis(shardedJedis);
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
if(null != shardedJedis){
shardedJedis.close();
}
}
return null;
}
/**
* attention:真正封装的方法,非常的简洁干脆
*/
public String set(final String key, final String value){
return execute(new ConnectionCallback<String>() {
@Override
public String doInRedis(
ShardedJedis shardedJedis) {
return shardedJedis.set(key, value);
}
});
}
public String get(final String key){
return execute(new ConnectionCallback<String>(){
@Override
public String doInRedis(ShardedJedis shardedJedis) {
return shardedJedis.get(key);
}
});
}
}
通过上面的代码,我们可以清晰的看到,将
try
,
catch
,
finally
部分的公共代码都封装到了回调函数中,当调用具体方法的时候,再实现回调方法的具体业务逻辑即可,代码的复用率更高了。
如果大家对spring jdbc或者是spring data redis的源码研究过,就应该知道JdbcTemplate和RedisTemplate这两个类,这两个框架中用到了大量的callback机制,下面我们就以spring data redis为例,来简单的看下高手是如何玩转callback机制的。
首先定义回调方法,代码如下:
public interface RedisCallback<T> {
T doInRedis(RedisConnection connection) throws DataAccessException;
}
其次,定义操作方法,代码如下:
public interface RedisOperations<K, V> {
<T> T execute(RedisCallback<T> action);
<T> T execute(SessionCallback<T> session);
…………省略若干方法…………
}
最后,回调机制的实现
RedisTemplate
类,代码如下:public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V> {
// 以下定义的是Redis支持的操作类型,例如SetOperations就是用来操作Set类型的,由于Redis支持的操作类型比较多,所以将每种操作类型都抽象成一个具体的操作类
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;
/**
* Constructs a new <code>RedisTemplate</code> instance.
*/
public RedisTemplate() {}
public <T> T execute(RedisCallback<T> action) {
return execute(action, isExposeConnection());
}
public <T> T execute(RedisCallback<T> action, boolean exposeConnection) {
return execute(action, exposeConnection, false);
}
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));
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.unbindConnection(factory);
} else {
RedisConnectionUtils.releaseConnection(conn, factory);
}
}
}
public <T> T execute(SessionCallback<T> session) {
Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(session, "Callback object must not be null");
RedisConnectionFactory factory = getConnectionFactory();
// bind connection
RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
try {
return session.execute(this);
} finally {
RedisConnectionUtils.unbindConnection(factory);
}
}
…………省略若干创建连接代码…………
…………以下是具体的操作,会调用回调方法…………
protected List<Object> execRaw() {
return execute(new RedisCallback<List<Object>>() {
public List<Object> doInRedis(RedisConnection connection) throws DataAccessException {
return connection.exec();
}
});
}
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);
}
public void delete(Collection<K> keys) {
if (CollectionUtils.isEmpty(keys)) {
return;
}
final byte[][] rawKeys = rawKeys(keys);
execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) {
connection.del(rawKeys);
return null;
}
}, true);
}
}
通过上面的示例,大家应该对
callback
机制有了一定的了解,最后,一言以蔽之
--
如果你调用我,那么我就回调。