详解java模板和回调机制
作者:ngulc 发布时间:2023-08-13 15:33:46
最近看spring的JDBCTemplete的模板方式调用时,对模板和回调产生了浓厚兴趣,查询了一些资料,做一些总结。
回调函数:
所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机。一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供。由于S并不知道C提供的B姓甚名谁,所以S会约定B的接口规范(函数原型),然后由C提前通过S的一个函数R告诉S自己将要使用B函数,这个过程称为回调函数的注册,R称为注册函数。Web Service以及Java 的RMI都用到回调机制,可以访问远程服务器程序。回调函数包含下面几个特性:
1、属于工作流的一个部分;
2、必须按照工作流指定的调用约定来申明(定义);
3、他的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能;
回调机制:
回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。
java回调机制:
软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。
同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;
回 调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;
异步调用:一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。
回调和异步调用的关系非常紧密:使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。
回调实例
1、回调接口
public interface Callback {
String callBack();
}
2、调用者
public class Another {
private Callback callback;
//调用实现类的方法
public void setCallback(Callback callback) {
this.callback = callback;
}
//业务需要的时候,通过委派,来调用实现类的具体方法
public void doCallback(){
System.out.println(callback.callBack());
}
}
3、测试回调函数
public class TestCallcack {
public static void main(String[] args) {
//创建调用者的实现类
Another another = new Another();
//将回掉接口注册到实现类中
another.setCallback(new Callback() {
@Override
public String callBack() {
return "you are a pig";
}
});
//执行回调函数
another.doCallback();
}
}
回调方法的使用通常发生在“java接口”和“抽象类”的使用过程中。模板方法设计模式就使用方法回调的机制,该模式首先定义特定的步骤的算法骨架,而将一些步骤延迟到子类中去实现的设计模式。模板方法设计模式使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
模板方式设计模式的适用性:
1、一次性实现一个算法的不变部分,并将可变的算法留给子类来实现。
2、各子类中公共的行为应该被提取出来并集中一个公共父类中以避免代码重复。
3、可以控制子类扩展。
模板实例:
抽象模板方法类:
public abstract class AbstractSup {
//需要子类实现的方法
public abstract void print();
//模板方法
public void doPrint(){
System.out.println("执行模板方法");
for (int i = 0; i < 3; i++) {
print();
}
}
}
子类实现模板方式类:
public class SubClass extends AbstractSup{
@Override
public void print() {
System.out.println("子类的实现方法");
}
}
模板方法测试类:
public class TempleteTest {
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.print();
subClass.doPrint();
}
}
下面深入介绍下spring模板方法的使用,以JdbcTemplete为例,详细说明模板模式和回调机制的使用。
首先看一下经典的JDBC编程的例子:
public List<User> query() {
List<User> userList = new ArrayList<User>();
String sql = "select * from User";
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
con = HsqldbUtil.getConnection();
pst = con.prepareStatement(sql);
rs = pst.executeQuery();
User user = null;
while (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(!con.isClosed()){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return userList;
}
一个简单的查询,就要做这么一大堆事情,而且还要处理异常,我们不防来梳理一下:
1、获取connection
2、获取statement
3、获取resultset
4、遍历resultset并封装成集合
5、依次关闭connection,statement,resultset,而且还要考虑各种异常等等。
如果是多个查询会产生较多的重复代码,这时候就可以使用模板机制,通过观察我们发现上面步骤中大多数都是重复的,可复用的,只有在遍历ResultSet并封装成集合的这一步骤是可定制的,因为每张表都映射不同的java bean。这部分代码是没有办法复用的,只能定制。
抽象类代码:
public abstract class JdbcTemplate {
//模板方法
public final Object execute(String sql) throws SQLException{
Connection con = HsqldbUtil.getConnection();
Statement stmt = null;
try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(sql);
Object result = doInStatement(rs);//抽象方法(定制方法,需要子类实现)
return result;
}
catch (SQLException ex) {
ex.printStackTrace();
throw ex;
}
finally {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(!con.isClosed()){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//抽象方法(定制方法)
protected abstract Object doInStatement(ResultSet rs);
}
这个抽象类中,封装了SUN JDBC API的主要流程,而遍历ResultSet这一步骤则放到抽象方法doInStatement()中,由子类负责实现。
子类实现代码:
public class JdbcTemplateUserImpl extends JdbcTemplate {
@Override
protected Object doInStatement(ResultSet rs) {
List<User> userList = new ArrayList<User>();
try {
User user = null;
while (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
return userList;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
我们在doInStatement()方法中,对ResultSet进行了遍历,最后并返回。
测试代码:
String sql = "select * from User";
JdbcTemplate jt = new JdbcTemplateUserImpl();
List<User> userList = (List<User>) jt.execute(sql);
模板机制的使用到此为止,但是如果每次调用jdbcTemplate时,都要继承一下上面的父类,这样挺不方便的,这样回调机制就可以发挥作用了。
所谓回调,就是方法参数中传递一个接口,父类在调用此方法时,必须调用方法中传递的接口的实现类。
回调加模板模式实现
回调接口:
public interface StatementCallback {
Object doInStatement(Statement stmt) throws SQLException;
}
模板方法:
public class JdbcTemplate {
//模板方法
public final Object execute(StatementCallback action) throws SQLException{
Connection con = HsqldbUtil.getConnection();
Statement stmt = null;
try {
stmt = con.createStatement();
Object result = action.doInStatement(rs);//回调方法
return result;
}
catch (SQLException ex) {
ex.printStackTrace();
throw ex;
}
finally {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(!con.isClosed()){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public Object query(StatementCallback stmt) throws SQLException{
return execute(stmt);
}
}
测试的类:
public Object query(final String sql) throws SQLException {
class QueryStatementCallback implements StatementCallback {
public Object doInStatement(Statement stmt) throws SQLException {
ResultSet rs = stmt.executeQuery(sql);
List<User> userList = new ArrayList<User>();
User user = null;
while (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
return userList;
}
}
JdbcTemplate jt = new JdbcTemplate();
return jt.query(new QueryStatementCallback());
}
为什么spring不用传统的模板方法,而加之以Callback进行配合呢?
试想,如果父类中有10个抽象方法,而继承它的所有子类则要将这10个抽象方法全部实现,子类显得非常臃肿。而有时候某个子类只需要定制父类中的某一个方法该怎么办呢?这个时候就要用到Callback回调了。
另外,上面这种方式基本上实现了模板方法+回调模式。但离spring的jdbcTemplate还有些距离。 我们上面虽然实现了模板方法+回调模式,但相对于Spring的JdbcTemplate则显得有些“丑陋”。Spring引入了RowMapper和ResultSetExtractor的概念。 RowMapper接口负责处理某一行的数据,例如,我们可以在mapRow方法里对某一行记录进行操作,或封装成entity。 ResultSetExtractor是数据集抽取器,负责遍历ResultSet并根据RowMapper里的规则对数据进行处理。 RowMapper和ResultSetExtractor区别是,RowMapper是处理某一行数据,返回一个实体对象。而ResultSetExtractor是处理一个数据集合,返回一个对象集合。
当然,上面所述仅仅是Spring JdbcTemplte实现的基本原理,Spring JdbcTemplate内部还做了更多的事情,比如,把所有的基本操作都封装到JdbcOperations接口内,以及采用JdbcAccessor来管理DataSource和转换异常等。


猜你喜欢
- Java InputStream的任意拷贝有时候,当我们需要多次使用到同一个InputStream的时候如何实现InputStream的拷贝
- activity动画方式在AndroidMenifest中添加activity的动画属性windowAnimationStyle <i
- 添加maven依赖<?xml version="1.0" encoding="UTF-8"?&
- 本文将介绍一种通过代码控制ListView上下滚动的方法。先上图:按下按钮会触发ListView滚动或停止。实现该功能并不难,下面给出主要代
- 本文主要是自定义了EditText,当EditText有文本输入的时候会出现删除图标,点击删除图标实现文本的清空,其次对密码的返回做了处理,
- 前言SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文
- @Profiles和@PropertySource根据环境切换配置文件使用@PropertySource注解加载配置文件,并制定解析配置文件
- Java 虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途,如图所示:程序计数器程序计数
- 这篇文章主要介绍了Spring自定义参数解析器代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 前言通过Mybatis做7000+数据量的批量插入的时候报错了,error log如下: , ('G61010352', &
- 先看看效果图:实现思路:擦除图片相应的角,然后层叠图片,产生倾斜效果代码实现:1、定义属性在values文件夹下的attrs文件添加以下代码
- 概述java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的
- 本文实例为大家分享了Android自定义View实现标签流效果的具体代码,供大家参考,具体内容如下一、概述Android自定义View实现标
- 本文主要介绍了Spring Boot 应用可视化监控,分享给大家,具体如下:1、Spring Boot 应用暴露监控指标【版本 1.5.7.
- 前言上一篇文章分享了Springboot项目快速实现过滤器功能,本篇文章接着来盘一盘 * ,仔细研究后会发现,其实 * 和过滤器的功能非常类
- 通常,我们会被要求实现类似支付宝首页的特效:随着界面的滑动,标题栏的背景透明度渐变。在实际开发中,常见的滑动有列表RecyclerView(
- 前言值类型和引用类型,是c#比较基础,也必须掌握的知识点,但是也不是那么轻易就能掌握,今天跟着老胡一起来看看吧。 典型类型首先我们
- 最近遇到的一个场景,在一个被 @Transactional 注解的方法A中中调用了一个被 @Async 注解标记的方法B,由于方法B 在执行
- 前言使用Java8的新特性Stream流式处理,可以提高对于集合的一些操作效率,再配合lambda表达式,可以极致的简化代码,尤其还有并行流
- 1. 调试追踪代码: public static void enterTryMethod() { S