源码里的模板方法设计模式,JdbcTemplate、RedisTemplate、RestTemplate源码分析

  • 前言:设计模式是阅读源码的一项极其重要的内功心法,而其中模板方法设计模式的套路在JDK、Spring、Mybatis、Maven中被广泛使用,如:Spring中的各种Template,有JdbcTemplate、RedisTemplate、RestTemplate皆是这种老把戏,假如您对该内功心法了如指掌,那么阅读它们的源码简直快地飞起,甚至能依葫芦画瓢,设计出HttpTemplate、FtpTemplate根本不在话下。
  • 模板方法设计模式简介:在一个方法里定义了做事的流程,该流程中的某些步骤的实现逻辑是固定不变的,某些步骤的实现逻辑是变化的,即其对应的方法是抽象方法,具体的逻辑推迟到子类中实现,传统的模板方法模式类图如下:

源码里的模板方法设计模式,JdbcTemplate、RedisTemplate、RestTemplate源码分析_第1张图片

  • JdbcTemplate:

回顾下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与传统的模板方法模式的继承结构稍有不同,其引入了回调接口的概念,将需子类实现的逻辑抽象到回调接口中,通过入参的方式将子类的实现逻辑传入模板方法。

  • RedisTemplate:

这里读者可以思考下,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等。

  • RestTemplate:

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相关源码时,将会事半功倍。

你可能感兴趣的:(设计模式,java)