模版模式在Spring中的应用

news2024/9/21 13:02:39

前言

模式模式在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的类图

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

在JdbcTemplate中实现了execute方法

@Override
	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);
			}
			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<T> {

	/**
	 * 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.
	 * <p><b>NOTE:</b> 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.
	 * <p>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.
	 * <p>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<T> action)调用的地方,分别对应查询,更新和批量更新操作

 我们到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<Integer>, 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> 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);
					}
					return rse.extractData(rsToUse);
				}
				finally {
					JdbcUtils.closeResultSet(rs);
				}
			}
			@Override
			public String getSql() {
				return sql;
			}
		}

		return execute(new QueryStatementCallback());
	}

总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/759381.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python学习(七)

#字典 #字典的定义&#xff0c;同样是使用{}&#xff0c;不过存储的元素是一个个的&#xff1a;键值对。如下语法&#xff1a; my_dict{"周杰伦":99,"林俊杰":99} print(f"字典的内容是&#xff1a;{my_dict},类型是&#xff1a;{type(my_dict)}"…

Vue条件渲染

v-if 和 v-show <body> <div id"root"><!--用v-show做条件渲染--> <!-- <h2 v-show"false">欢迎来到{{name}}</h2>--><!--使用v-if做条件渲染--><h2 v-if"false">欢迎来到{{name}}</h2&g…

一文读懂STM32芯片总线

目录 一、前言 二、总线基础知识概述 (1)、总线在芯片中的角色 (2)、总线的类型 (3)、总线的指标 (4)、AHB和APB 三、总线框架结构 (1)、结构类型 (2)、总线模块 (3)、总线交互 四、总结 一、前言 本篇介绍STM32芯片内部的总线系统结构&#xff0c;嵌入式芯片内部的…

C++11左值和右值、左值引用和右值引用浅析

从字面意思来讲&#xff0c;左值就是“能用在赋值语句等号左侧的内容&#xff08;它得代表一个地址&#xff09;”&#xff1b;右值就是不能作为左值的值&#xff0c;即右值不能出现在赋值语句中等号的左侧。C中的一条表达式&#xff0c;要么就是右值&#xff0c;要么就是左值&…

钉钉监控Hippo4j线程池通知报警

&#x1f680; 线程池管理工具-Hippo4j &#x1f680; &#x1f332; AI工具、AI绘图、AI专栏 &#x1f340; &#x1f332; 如果你想学到最前沿、最火爆的技术&#xff0c;赶快加入吧✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;CSDN-Java领域优质创作者&#…

软考03海明校验码

文章目录 前言一、练习一二、练习二总结 前言 海明校验码可以用来纠正错误 公式&#xff1a; 2k-1≥nk n为数据位&#xff0c;k为校验位 编码规则&#xff1a;校验位为2的次方&#xff0c;其他为数据位。 一、练习一 数据位为4&#xff0c;校验位最小为多少位&#xff1f; 答&…

《微服务架构设计模式》第十二章 部署微服务应用

内容总结自《微服务架构设计模式》 部署微服务应用 一、部署模式分类二、编程语言特定的发布包格式1、概述2、利弊 三、将服务部署为虚拟机1、概览2、利弊 四、将服务部署为容器1、概述2、利弊3、K8S部署 五、Serverless部署1、概述2、利弊3、示例 六、总结 一、部署模式分类 …

计算机丢失msvcp140.dll是什么意思?哪个修复方法更推荐

打开photoshop软件的时候&#xff0c;计算报错丢失msvcp140.dll是什么意思&#xff1f;软件也无法正常启动运行&#xff0c;这个主要的原因就是电脑系统中的msvcp140.dll文件丢失或者损坏了&#xff0c;运行需要该文件的程序或应用程序时&#xff0c;操作系统无法找到该特定的动…

低代码都做了什么?怎么实现 Low-Code?

前言 低代码的概念早在很多很多年前就已经出现了&#xff0c;比如最早期的Dreamweaver 1.0&#xff0c;使用这种可视化编辑工具根本不需要投入较高的学习成本就可以轻松实现一个Web页面。而低代码最大的初衷也正是让开发者或用户减少编码时间&#xff0c;从而把更多的时间和精力…

Python应用实例(二)数据可视化(五)

数据可视化&#xff08;五&#xff09;制作全球地震散点图&#xff1a;JSON格式 1.地震数据2.查看JSON数据3.创建地震列表4.提取震级5.提取位置数据6.绘制震级散点图7.另一种指定图表数据的方式 下载一个数据集&#xff0c;其中记录了一个月内全球发生的所有地震&#xff0c;再…

Unity3d-路径巡逻

使用U3D实现的简单巡逻方法 游戏对象逐个向组成路径的节点进行直线移动两种巡逻方案 根据列表顺序移动&#xff0c;到达最后一个后&#xff0c;直接返回第一个&#xff0c;重新开始循环根据列表顺序移动&#xff0c;到达最后一个后&#xff0c;根据顺序反向移动到第一个&#…

用 Pytest+Appium+Allure 做 UI 自动化的那些事~(有点干)

目录 前言&#xff1a; Appium 不常见却好用的方法 Appium 直接执行 adb shell 方法 Appium 直接截取元素图片的方法 Appium 直接获取手机端日志 Appium 直接与设备传输文件 Pytest 与 Unittest 初始化上的区别 1.Pytest 与 unitest 类似&#xff0c;有些许区别&#x…

【嘉立创EDA】客户端版本嘉立创专业版半离线版更新方法

文章路标👉 文章解决问题主题内容工程文件备份保护问题新版本更新通知文章解决问题 1️⃣ 嘉立创EDA专业版,是时下越发常用的PCBA设计软件之一。该环境除了在规划的设计开发过程中,为响应各用户的建议、需求,其迭代更新速度也是很快。为了使用最新的功能,用户需要使用最…

C++学习——类和对象(一)

C语言和C语言最大的区别在于在C当中引入了面向对象的编程思想&#xff0c;想要完全了解c当中的类和对象&#xff0c;就要从头开始一点一点的积累并学习。 一&#xff1a;什么是面向对象编程 我们之前学习的C语言属于面向过程的编程方法。举一个简单的例子来说&#xff1a;面向过…

Linux地盘上AMD处理器称王了

近日资讯&#xff0c;尽管从全局来看&#xff0c;Linux系统份额远不及Windows&#xff0c;但在程序员、开发者、硬核玩家圈子&#xff0c;Linux则备受推崇。 来自Steam的最新数据显示&#xff0c;在Linux游戏用户中&#xff0c;AMD处理器的份额占据绝对优势&#xff0c;达到了…

Spring Cloud—GateWay之限流

RequestRateLimiter RequestRateLimiter GatewayFilter 工厂使用 RateLimiter 实现来确定是否允许当前请求继续进行。如果不允许&#xff0c;就会返回 HTTP 429 - Too Many Requests&#xff08;默认&#xff09;的状态。 这个过滤器需要一个可选的 keyResolver 参数和特定于…

cpuset.cpus.effective: no such file or directory (修改 docker cgroup 版本的方法)

要切换使用 v1 版 cgroup&#xff0c;需要做如下配置&#xff1a; vim /etc/default/grubGRUB_CMDLINE_LINUX"systemd.unified_cgroup_hierarchy0"update-grubreboot完美解决

【条件与循环】——matlab入门

目录索引 if&#xff1a;else与elseif&#xff1a; for&#xff1a; if&#xff1a; if 条件语句块 endelse与elseif&#xff1a; if 条件代码块 elseif 条件代码块 else 代码块 endfor&#xff1a; for 条件循环体 end在matlab里面类似的引号操作都是包头又包尾的。上面的c…

TypeScript基础篇 - TS介绍

目录 Typescript的定义 type.ts 深入了解Typescript Typescript应该学到什么程度&#xff1f; Typescript学习方法 如何学好TS 小节&#xff1a;常见学习误区 一张ai生成图~ Typescript的定义 2012年微软发布的一门编程语言 Transcompiler【翻译编译器】Typescript——…

操作符详解(2)

文章目录 8. 条件操作符9. 逗号表达式10. 下标引用、函数调用和结构成员11. 表达式求值11.1 隐式类型转换11.2 算术转换11.3 操作符的属性 附&#xff1a; 8. 条件操作符 exp1 ? exp2 : exp3 int main() {int a 0;int b 0;if (a > 5){b 3;}else{b -3;}//(a > 5) ? …