从Spring乘舟回调到Jedis自我发育回调

从Spring乘舟回调到Jedis自我发育回调

    最近这段时间,接触回调模式比较多,使用也比较多,所以今天来简单写一下关于回调的思考。在实际开发中,回调是一种常见且常用的方式,用来实现把一块代码传递到其他代码处来执行。

下面是维基百科对回调的定义:

在计算机程序设计中,回调函数,或简称回调(Callback 即call then back 被主函数调用运算后会返回主函数),是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。

Spring中的回调

    此处简要说一下Spring中在数据库操作方面的回调使用,涉及到JdbcTemplate 和 HibernateTemplate。这两个类都需要打开资源进行数据库操作,操作完后进行资源释放。

    我们在进行JDBC操作数据库的时候,往往会按照API写一大堆重叠的代码,为了进行一个简单的操作,就必须要重复写诸如打开连接、执行操作等代码。我们在写DAO层的时候,会对JDBC进行封装,依此来简化我们上层的使用。

    Spring也是本着封装和减少我们出错等的原则来设计了其内部中对于持久化的集成。尤其在其中的与JDB和Hibernate的融合上,在Spring中使用他们变得更简洁,我们无需手动关心资源的打开与释放等,这要得益于Spring在进行封装的时候使用了回调的一个小机制。

    此处我们以JdbcTemplate的部分代码来做分析,下面贴出来的源码只贴出了必要的部分。

    定义回调接口,用来标准化回调的接口和函数。

1
2
3
4
5
6
7
/**
* 回调接口,把基于此接口的实现类传入到JdbcTemplate的封装方法中,供回调使用
*/
public interface StatementCallback<T> {
T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}

    JdbcTemplate作为一个模板类,其中主要包括对JDBC操作流程的封装,还包括对外的调用方法,用来传入sql,参数等来执行JDBC操作。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
/**
* 把JDBC的基本操作流程封装起来,供其他方法直接调用,传入回调接口的实现类
* @param action
*/
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
/*
* 1、获取数据库连接Connection
*/
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
/*
* 2、创建SQL执行清单Statement
*/
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
// 3、执行Statement,获取操作结果
T result = action.doInStatement(stmtToUse);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
}
finally {
/*
* 4、释放资源
*/
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
/**
* 可供自定义的DAO中使用的execute方法,可以自己传入sql来执行自定义的sql操作
* @param sql
*/
public void execute(final String sql) throws DataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}
/*
* 实现SQL清单回调接口,定义回调函数
*/
class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
@Override
public Object doInStatement(Statement stmt) throws SQLException {
/*
* 执行SQL
*/
stmt.execute(sql);
return null;
}
@Override
public String getSql() {
return sql;
}
}
execute(new ExecuteStatementCallback());
}
}

    在我们的DAO中,就可以直接注入JdbcTemplate,来调用其中的execute(final String sql)方法来传递进入sql来执行,而无需考虑连接打开,连接释放等问题。

回调应用

    起初在研究到Spring中的回调时,并没有太留意,只是觉得很方便,可到了最近几天也在做类似的内容,才发现原来还是有相通的地方。最近这几天在研究Redis,准备在项目中加入简单的消息队列和对象缓存,刚开始写就发现在获取Jedis和归还Jedis上有些许可以改进的地方。

    用Java语言来操作Redis,选用了Jedis来作为客户端操作Redis。用到了Jedis中的连接池来操作,所以会涉及到获取Jedis对象,连接池资源的释放等问题,也有很多重复代码的存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public <T> T execute(参数){
T result = null;
Jedis jedis = null;
try {
jedis = this.getJedisPool().getResource();
result = jedis的各种操作
} catch (Exception e) {
e.printStackTrace();
} finally {
//返还到连接池
jedis.close();
return result;
}
}

如此一来便和JDBC应用时碰到了非常类似的问题,在做每一次实际操作的时候,操作之前和之后的代码都是相同的,唯独这些操作的代码是不一样的,所以就考虑抽取这些操作的代码出来,每次只要把这部分操作代码灵活的传递到Jedis代码执行处便可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 执行Redis操作
* @param callback 回调函数
* @param <T>
* @return
*/
public <T> T execute(JedisCallback<T> callback){
T result = null;
Jedis jedis = null;
try {
jedis = this.getJedisPool().getResource();
// 引用回调函数中的方法执行Redis操作
result = callback.doWithJedis(jedis);
} catch (Exception e) {
e.printStackTrace();
} finally {
//返还到连接池
jedis.close();
return result;
}
}

    所以就很自然的联想到了Spring中在JDBC和Hibernate方面的思想,用回调机制来进行传递操作内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 执行Redis操作的回调接口
*/
public interface JedisCallback<T> {
/**
* 用Jedis对象来进行Redis的操作
* @param jedis
* @return
*/
T doWithJedis(Jedis jedis);
}

    再定义一个JedisTemplate类来声明各种操作Redis的方法,然后在每个方法中定义局部类实现回调接口来实现具体Redis操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 设置多个哈希值
* @param key
* @param hash
* @return
*/
public String setMHash(String key, Map<String, String> hash) {
class SetMultiHashCallback implements JedisCallback<String>{
@Override
public String doWithJedis(Jedis jedis) {
return jedis.hmset(key, hash);
}
}
return jedisManager.execute(new SetMultiHashCallback());
}

    通过这样一种简单的封装,便可以对程序中的其他模块灵活的提供服务而无需让其他地方关心资源等问题。

    从本质上来说,回调都是一样的,只是不同语言可能有不同的表现形式,但只要抓住精髓,举一反三,便可熟练掌握,不管推塔还是打水晶,便都可以不在话下。

大爷给小弟的零花钱
显示 Gitment 评论