模版模式在Spring中的应用

前言

模版模式在Spring中的应用较多,这里结合JdbcTemplate的源码来和大家一起学习下,更加深刻认识下模版模式,以便在日常开发中,能灵活运用模版模式,来减少重复代码,增强代码的可拓展性。

何为模版模式

经典模板方法定义:

父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。最大的好处:代码复用,减少重复代码。除了子类要实现的特定方法,其他方法及方法调用顺序都在父类中预先写好了。所以父类模板方法中有两类方法:

共同的方法: 所有子类都会用到的代码

不同的方法: 子类要覆盖的方法,分为两种:

  • 抽象方法:父类中的是抽象方法,子类必须覆盖
  • 钩子方法:父类中是一个空方法,子类继承了默认也是空的

注:为什么叫钩子,子类可以通过这个钩子(方法),控制父类,因为这个钩子实际是父类的方法(空方法)!

public abstract class AbstractTemplate {
    
    public void templateMethod() {
        // 执行一些通用的操作
        
        step1();
        step2();
        step3();
        
        // 执行一些通用的操作
    }
    
    protected abstract void step1();
    
    protected abstract void step2();
    
    protected abstract void step3();
}

public class ConcreteTemplate extends AbstractTemplate {
    
    protected void step1() {
        // 实现具体的业务逻辑
    }
    
    protected void step2() {
        // 实现具体的业务逻辑
    }
    
    protected void step3() {
        // 实现具体的业务逻辑
    }
}

public class Client {
    public static void main(String[] args) {
        AbstractTemplate template = new ConcreteTemplate();
        template.templateMethod();
    }
}

在上面的示例中,AbstractTemplate是一个模板类,定义了一个templateMethod()方法作为模板方法。该方法中包含了通用的操作,以及调用了三个抽象方法(step1()、step2()、step3())来完成具体的业务逻辑。ConcreteTemplate是一个具体的模板类,实现了三个抽象方法。在Client类中,我们实例化了ConcreteTemplate对象,并调用了templateMethod()方法,这样就完成了整个模板方法模式的使用。通过使用模板方法模式,在Spring中我们可以在不改变模板类的情况下,通过实现不同的回调方法来定制特定的业务逻辑,从而达到代码的复用和扩展的目的。

Spring是如何使用模版模式?

Spring模板方法模式实质:是模板方法模式和回调模式的结合,是Template Method不需要继承的另一种实现方式。Spring几乎所有的外接扩展都采用这种模式。我们先来看看JdbcTemplate的类图

模版模式在Spring中的应用_第1张图片

 在JdbcOperations接口中定义execute抽象方法

模版模式在Spring中的应用_第2张图片

在JdbcTemplate中实现了execute方法

@Override
	public  T execute(StatementCallback action) throws DataAccessException {
		Assert.notNull(action, "Callback object must not be null");

		Connection con = DataSourceUtils.getConnection(getDataSource());
		Statement stmt = null;
		try {
			Connection conToUse = con;
			if (this.nativeJdbcExtractor != null &&
					this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
				conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
			}
			stmt = conToUse.createStatement();
			applyStatementSettings(stmt);
			Statement stmtToUse = stmt;
			if (this.nativeJdbcExtractor != null) {
				stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
			}
			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 {
			JdbcUtils.closeStatement(stmt);
			DataSourceUtils.releaseConnection(con, getDataSource());
		}
	}

 在上面的方法中,一些通用的建立链接,创建Statement对象,释放Statement对象,关闭链接代码都是一些固定的逻辑。实际有变化的逻辑都在T result = action.doInStatement(stmtToUse);中注意看这里的action,是一个回调对象,我们可以看下这个对象的定义

public interface StatementCallback {

	/**
	 * Gets called by {@code JdbcTemplate.execute} with an active JDBC
	 * Statement. Does not need to care about closing the Statement or the
	 * Connection, or about handling transactions: this will all be handled
	 * by Spring's JdbcTemplate.
	 * 

NOTE: Any ResultSets opened should be closed in finally blocks * within the callback implementation. Spring will close the Statement * object after the callback returned, but this does not necessarily imply * that the ResultSet resources will be closed: the Statement objects might * get pooled by the connection pool, with {@code close} calls only * returning the object to the pool but not physically closing the resources. *

If called without a thread-bound JDBC transaction (initiated by * DataSourceTransactionManager), the code will simply get executed on the * JDBC connection with its transactional semantics. If JdbcTemplate is * configured to use a JTA-aware DataSource, the JDBC connection and thus * the callback code will be transactional if a JTA transaction is active. *

Allows for returning a result object created within the callback, i.e. * a domain object or a collection of domain objects. Note that there's * special support for single step actions: see JdbcTemplate.queryForObject etc. * A thrown RuntimeException is treated as application exception, it gets * propagated to the caller of the template. * @param stmt active JDBC Statement * @return a result object, or {@code null} if none * @throws SQLException if thrown by a JDBC method, to be auto-converted * to a DataAccessException by a SQLExceptionTranslator * @throws DataAccessException in case of custom exceptions * @see JdbcTemplate#queryForObject(String, Class) * @see JdbcTemplate#queryForRowSet(String) */ T doInStatement(Statement stmt) throws SQLException, DataAccessException; }

可以看下这里execute(StatementCallback action)调用的地方,分别对应查询,更新和批量更新操作

模版模式在Spring中的应用_第3张图片

 我们到UpdateStatementCallback中去看下

	@Override
	public int update(final String sql) throws DataAccessException {
		Assert.notNull(sql, "SQL must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Executing SQL update [" + sql + "]");
		}

		class UpdateStatementCallback implements StatementCallback, SqlProvider {
			@Override
			public Integer doInStatement(Statement stmt) throws SQLException {
				int rows = stmt.executeUpdate(sql);
				if (logger.isDebugEnabled()) {
					logger.debug("SQL update affected " + rows + " rows");
				}
				return rows;
			}
			@Override
			public String getSql() {
				return sql;
			}
		}

		return execute(new UpdateStatementCallback());
	}

可以看到UpdateStatementCallback 实现了StatementCallback接口,将具体update操作逻辑放在了execute外层,这样就是将变化作为回调对象传入到execute方法中。可以到QueryStatementCallback 看下,亦是同理

	@Override
	public  T query(final String sql, final ResultSetExtractor rse) throws DataAccessException {
		Assert.notNull(sql, "SQL must not be null");
		Assert.notNull(rse, "ResultSetExtractor must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Executing SQL query [" + sql + "]");
		}

		class QueryStatementCallback implements StatementCallback, SqlProvider {
			@Override
			public T doInStatement(Statement stmt) throws SQLException {
				ResultSet rs = null;
				try {
					rs = stmt.executeQuery(sql);
					ResultSet rsToUse = rs;
					if (nativeJdbcExtractor != null) {
						rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
					}
					return rse.extractData(rsToUse);
				}
				finally {
					JdbcUtils.closeResultSet(rs);
				}
			}
			@Override
			public String getSql() {
				return sql;
			}
		}

		return execute(new QueryStatementCallback());
	}

总结

为什么JdbcTemplate没有使用继承?因为这个类的方法太多,但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接,那么我们怎么办呢?

模版模式在Spring中的应用_第4张图片

 我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate,从而完成了调用。

你可能感兴趣的:(spring,Spring,JdbcTemplate,模版模式)