带你在项目中玩转设计模式之模板模式(JdbcTemplate为例,Service层实际案例)

一、模板方法模式是什么?有哪些应用场景?

模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。它适应的场景有:一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。(百度百科)。

看到定义或许会很迷糊,我们可以通过例子说明,其实模板模式的一个典型的应用就是Spring的JdbcTemplate,这是一个经典的模板模式,我们就以Spring的JdbcTemplate为例子介绍模板模式,在介绍JdbcTemplate之前我们可以先想一下Java对数据库的操作包括哪几部分?数据库的操作可以简单的分为以下几部分:数据源的配置,数据库连接的获取,SQL的执行,结果集的处理,这四步只有结果集会有不同的处理,前三个执行逻辑基本上都是一样的。因此我们可以将前三步封装成一个模板即JdbcTemplate,结果集的实现则交给调用者自己实现。

简单模板模式实现,可以看我另外一篇:设计模式--【模板模式】

JdbcTemplate源码

我们可以查看JdbcTemplate的源码:

public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
    ......
}

上面的源码中JdbcAccessor是对数据源的管理,JdbcOperations则是对数据库操作接口定义,JdbcTemplate实现了JdbcOperations将调用逻辑封装成一个模板,如下为JdbcAccessor和JdbcOperations部分源码。更多源码可以参考Spring源码。
 

public abstract class JdbcAccessor implements InitializingBean {
    protected final Log logger = LogFactory.getLog(getClass());
    private DataSource dataSource;
    private SQLExceptionTranslator exceptionTranslator;
    private boolean lazyInit = true;
    public void setDataSource(DataSource dataSource) {
	this.dataSource = dataSource;
    }
    public DataSource getDataSource() {
	return this.dataSource;
    }
    //更多方法参考Spring源码
    ......
}
public interface JdbcOperations {
    .....
    <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException;
    <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException;
    //更多定义查看Spring源码
    ......
}

接下来就是模板方法的封装,我们以上面的List<T> query(String sql, RowMapper<T> rowMapper)方法为例,查看是怎么封装模板方法的。

public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
    //调用下面的query方法,
    return query(sql, new RowMapperResultSetExtractor<T>(rowMapper));
}
//该方法也没有执行数据库操作,而是交给execute(StatementCallback<T> action)方法
public <T> T query(final String sql, final ResultSetExtractor<T> 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<T>, 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);
		}
                //将查询的结果集交给RowMapperResultSetExtractor实例处理
		return rse.extractData(rsToUse);
		}finally {
		    JdbcUtils.closeResultSet(rs);
		    }
		}
		@Override
		public String getSql() {
			return sql;
		}
    }
    return execute(new QueryStatementCallback());
}
//真正查询数据库的方法
public <T> T execute(StatementCallback<T> 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);
	}
        //创建Statement
	stmt = conToUse.createStatement();
	applyStatementSettings(stmt);
	Statement stmtToUse = stmt;
	if (this.nativeJdbcExtractor != null) {
	    stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
	}
        //执行SQL语句调用的是上面方法的内部类,QueryStatementCallback 
	T result = action.doInStatement(stmtToUse);
	handleWarnings(stmt);
	return result;
	}catch (SQLException ex) {
	    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());
    }
}

阅读上面的源码,最终结果集的处理交给了ResultSetExtractor的extractData方法,我们再看该方法的实现:

@Override
public List<T> extractData(ResultSet rs) throws SQLException {
    List<T> results = (this.rowsExpected > 0 ? new ArrayList<T>(this.rowsExpected) : new ArrayList<T>());
    int rowNum = 0;
    while (rs.next()) {
        //最终调用RowMapper接口的mapRow方法实现数据的转换,RowMapper需要我们自己实现
        results.add(this.rowMapper.mapRow(rs, rowNum++));
    }
    return results;
}

上面方法使用RowMapper的mapRow实现结果集与对象之间的转换,如下我们查询一个一个消息记录的例子:

jdbcTemplate.query("select * from sms", new RowMapper<Sms>(){
    @Override
    public Sms mapRow(ResultSet rs, int rowNum) throws SQLException {
        //自己实现之后返回要查询的对象
        return null;
    }			
});

利用模板方法将相同处理逻辑的代码放到抽象父类中,以提高代码的复用性。将不同的代码使用不同的子类实现,通过对子类的扩展增加新的行为,提高代码的扩展性。很好的实现了开闭原则。但是随着不同代码的增多,需要每一个抽象类都需要一个子类来实现,这样导致类的个数增加。增加了系统的复杂性。

案例

一、使用模板方法模式统一调用外部服务接口

/**
 * @Title:调用外部服务基类
 */
public abstract class AbstractInvokeOuterSystemService<T extends OuterBaseRequest,R extends OuterBaseResponse> implements InvokeOuterSystemService<T,R>{


    @Override
    public R request(T t) throws Exception {
        //1. 创建请求报文(不用的服务接口可能要进行的入参校验等,需要子类实现)
        String reqMessage = this.createMessage(t);
        //2. 发送请求报文(这个步骤可以共用)
        String respMessage = HttpClientUtil.doPostJson("http://url", reqMessage);
        //3. 将响应报文解析为响应信息对象(不用的服务接口需要响应不同的信息,需要子类实现)
        R resp = this.parseResponse(respMessage);
        return resp;
    }

    protected abstract String createMessage(T t);

    protected abstract R parseResponse(String responseMsg);

}

二、使用模板方法模式对外提供的统一接口服务

/**
 * @Title:对外提供的统一接口服务基类
 */
public abstract class AbstractFacadeApiService<T extends BaseRequest,R> implements FacadeApiService<JSONObject>{


    /**
     * 处理业务请求
     * @param requestMessage 请求报文对象(这个请求报文对象需要在Controller中对请求json报文做初步的解析,才可以得到)
     * @return
     */
    @Override
    public String handle(RequestMessage<JSONObject> requestMessage) {
        //这里可以做一些入参校验、验签等,主要针对请求头信息

        //1. 解析请求体信息
        T t = this.parseJSONObject(requestMessage.getData());
        //2. 执行业务逻辑(不同的服务接口处理逻辑不同,所以)
        ResponseMessage<R> responseMessage = this.doHandle(t);
        //3.将响应信息序列化为json串(此步骤可以共用)
        String responseMsg = JSONObject.toJSONString(responseMessage);
        return responseMsg;
    }

    protected abstract ResponseMessage<R> doHandle(T req);

    protected abstract T parseJSONObject(JSONObject jsonObject);


}

三、使用模板方法模式实现通知服务

/**
 * @Title:通知服务基类
 */
public abstract class AbstractNoticeService  implements NoticeService<NoticeResult>{

    /**
     * 模拟数据字典中 不同系统的通知地址
     */
    private static Map<String,String> noticeUrlMap = new HashMap<>();
    static {
        noticeUrlMap.put("aaa","http://xxx/aaa/");
        noticeUrlMap.put("bbb","http://xxx/bbb/");
    }

    @Override
    public NoticeResult notice(NoticeMessage noticeMessage) throws Exception {

        NoticeResult noticeResult = null;

        //1.获取通知地址(不同系统的通知地址一般可以配置在数据字典中,可以从数据字典中获取)
        String noticeUrl = noticeUrlMap.get(noticeMessage.getSystemCode());

        //2.构建通知内容
        String noticeContent = this.buildNoticeContent(noticeMessage.getUniqueNo());

        //3.发送通知内容
        String result = HttpClientUtil.doPostJson(noticeUrl, noticeContent);
        if(!StringUtils.isEmpty(result)){
            try{
                noticeResult = JSON.parseObject(result, NoticeResult.class);
            }catch (Exception e){
                noticeResult = new NoticeResult("9001", "The result of notice is empty!");
            }
        }else{
            noticeResult = new NoticeResult("9001", "The noticeUrl is empty!");
        }

        //4.更新通知状态
        this.updateNoticeStatus(noticeResult, noticeMessage);
        return  noticeResult;
    }

    /**
     * 更新通知状态
     * @param noticeResult 通知结果
     * @param noticeMessage 通知的消息信息
     */
    protected abstract void updateNoticeStatus(NoticeResult noticeResult, NoticeMessage noticeMessage);

    /**
     * 根据通知消息ID构建消息通知内容
     * @param uniqueNo
     * @return
     */
    protected abstract String buildNoticeContent(String uniqueNo);
}

四、使用模板方法模式实现数据访问接口

/**
 * @Title:数据操作服务基类(继承了BaseService中的泛型类型)
 *  增删改查的实现需要两步:1.获取对应的Mapper 2.调用Mapper对应的方法实现增删改查
 */
public abstract class AbstractService<K,T> implements BaseService<K,T>{

    protected abstract BaseMapper<K,T> getBaseMapper();


    @Override
    public T selectByPrimaryKey(K k){
        return (T) getBaseMapper().selectByPrimaryKey(k);
    }

    @Override
    public List<T> selectList(T t){
        return getBaseMapper().selectList(t);
    }


    @Override
    public int deleteByPrimaryKey(K k){
        return getBaseMapper().deleteByPrimaryKey(k);
    }

    @Override
    public int insert(T t){
        return getBaseMapper().insert(t);
    }

    @Override
    public int updateByPrimaryKeySelective(T t){
        return getBaseMapper().updateByPrimaryKeySelective(t);
    }

}

模版类已经实现了父接口中的抽象方法,子类只需要继承模板类,专注实现子接口中定义的抽象方法即可

/**
 * @Title:账户信息服务接口实现类
 */
@Service
public class AccountInfoServiceImpl extends AbstractService<Long, AccountInfo> implements AccountInfoService {
    //这里需要注入AccountInfoMapper

    @Override
    protected BaseMapper<Long, AccountInfo> getBaseMapper() {
        return null;
    }
}

五、使用模板方法模式实现redis分布式锁

        // 获取账户锁
        long lockId = rCustomerAccountLock.nextId();
        String customerIdStr = String.valueOf(myCustomerId);
        if (!rCustomerAccountLock.tryLock(customerIdStr, lockId, tryTimeout, TimeUnit.MILLISECONDS)) {
            throw new KnowledgeException("该账户已经被锁定,请稍后");
        }
        try {
            // 具体业务处理
            ...
        } finally {
            rCustomerAccountLock.unlock(customerIdStr, lockId);
        }

总结

(1)模板方法的优点:

         1、模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。

         2、子类实现算法的某些细节,有助于算法的扩展。

         3、 通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。

(2)模板方法的不足: 

         1、每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。

(3)什么工具干什么活,所以每个设计模式或者思想都有它的用武之地,而模板方法的使用场景是:

         1、 在某些类的算法中,用了相同的方法,造成代码的重复。

         2、 控制子类扩展,子类必须遵守算法规则。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值