回顾下JDBC的操作流程,有加载驱动程序、获取连接、创建会话、执行SQL、处理结果、释放资源。倘若每次数据库操作都需要如此繁琐的步骤,那么程序员会疯,Spring对如此不友好的API设计早已看不下去,故针对原生JDBC进行了封装,即JdbcTemplate,JdbcTemplate的使用极其简单,一次数据库操作,基本只需一次API调用即可。
其封装思路正是采用了模板方法设计模式的套路,因为JDBC的操作明显就是一个按流程调用API的过程,其中获取资源、释放资源对所有的数据库操作都是一样的,只是SQL的执行不同而已。
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
}
JdbcAccessor:jdbc访问者,主要定义了DataSource数据源、SQLExceptionTranslator异常转换器;
JdbcOperations:定义了JdbcTemplate对外提供的所有接口;
JdbcTemplate中的核心方法,即模板方法,JdbcTemplate对外提供的API最终会调用该方法,它定义了获取资源、执行SQL、释放资源这一基本流程:
@Override
@Nullable
public T execute(StatementCallback action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
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.
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
该模板方法接收一个入参StatementCallback:该接口称为回调接口,也叫函数接口,只定义了一个方法:
@Nullable
T doInStatement(Statement stmt) throws SQLException, DataAccessException;
子类在实现该接口时,需要实现该方法,即执行SQL的相关逻辑,这样模板方法就会因doInstatement的不同逻辑而呈现不同的功能,如查询、更新等。
JdbcTemplate与传统的模板方法模式的继承结构稍有不同,其引入了回调接口的概念,将需子类实现的逻辑抽象到回调接口中,通过入参的方式将子类的实现逻辑传入模板方法。
这里读者可以思考下,Redis操作是不是也有固定的流程,答案是肯定的,即获取连接、执行命令、释放连接。RedisTemplate的类结构相较于JdbcTemplate略微复杂了一些,但主体结构还是一样的。
public class RedisTemplate extends RedisAccessor implements RedisOperations, BeanClassLoaderAware {
}
RedisAccessor:redis访问者,主要定义了连接工厂RedisConnectionFactory;
RedisOperations:定义了一些redis操作接口;
BeanClassLoaderAware:Spring Ioc的回调接口,用于Spring容器的功能扩展,这里用于在Spring容器启动时,设置RedisTemplate中的类加载器。
RedisTemplate中的核心方法,即模板方法,RedisTemplate对外提供的API最终会调用该方法,它定义了获取连接、执行命令、释放连接这一基本流程:
@Nullable
public T execute(RedisCallback 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");
RedisConnectionFactory factory = getRequiredConnectionFactory();
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 {
RedisConnectionUtils.releaseConnection(conn, factory);
}
}
该方法的入参首先是RedisCallback,同样是一个回调接口,只定义了一个方法:
@Nullable
T doInRedis(RedisConnection connection) throws DataAccessException;
子类在实现该接口时,需要实现该方法,即执行Redis命令的相关逻辑,这样模板方法就会因doInRedis(RedisConnection connection)的不同逻辑而呈现不同的功能,如set、increment等。另外RedisTemplate根据redis的不同数据结构将操作进行了分类,如ValueOperations、ListOperations、SetOperations等。
RedisTemplate的执行流程同样是:获取请求、设置请求、发送请求、得到响应、处理结果、释放资源,这又是模板方法的套路。
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
}
InterceptingHttpAccessor:带有拦截器属性的Http访问者,其继承HttpAccessor访问者,主要定义了拦截器列表和ClientHttpRequestFactory客户端Http请求工厂。
RestTemplate中的核心方法,即模板方法,RestTemplate对外提供的API最终会调用该方法,它定义了获取请求、设置请求、发送请求、得到响应、处理结果、释放资源这一基本流程:
@Nullable
protected T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor responseExtractor) throws RestClientException {
Assert.notNull(url, "'url' must not be null");
Assert.notNull(method, "'method' must not be null");
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
if (responseExtractor != null) {
return responseExtractor.extractData(response);
}
else {
return null;
}
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}
finally {
if (response != null) {
response.close();
}
}
}
同样入参中包含RequestCallback回调接口和ResponseExtrator响应结果萃取器,子类在实现该接口时,需要实现该方法,即设置请求、获取结果的相关逻辑,这样模板方法就会因doWithRequest(ClientHttpRequest request)和extractData(ClientHttpResponse response)的不同逻辑而呈现不同的功能,如get、post等。
可以发现获取资源和释放资源这类操作都是流程相关的,都可以套用模板方法设计模式,如果您来设计一个HttpTemplate或FtpTemplate,您会怎样设计呢。
模板方法设计模式作为阅读源码的内功心法,您如果摸清了它的套路,相信在阅读JDK,Spring、Maven相关源码时,将会事半功倍。