最近看spring的JDBCTemplete的模板方式调用时,对模板和回调产生了浓厚兴趣,查询了一些资料,作一些总结。java
回调函数:算法
所谓回调,就是客户程序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都用到回调机制,能够访问远程服务器程序。回调函数包含下面几个特性: spring
1、属于工做流的一个部分;sql
2、必须按照工做流指定的调用约定来申明(定义);编程
3、他的调用时机由工做流决定,回调函数的实现者不能直接调用回调函数来实现工做流的功能; 设计模式
回调机制:服务器
回调机制是一种常见的设计模型,他把工做流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。app
java回调机制:异步
软件模块之间老是存在着必定的接口,从调用方式上,能够把他们分为三类:同步调用、回调和异步调用。ide
同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;
回 调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;
异步调用:一种相似消息或事件的机制,不过它的调用方向恰好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。
回调和异步调用的关系很是紧密:使用回调来实现异步消息的注册,经过异步调用来实现消息的通知。
回调实例
一、回调接口
1 public interface Callback { 2 String callBack(); 3 }
二、调用者
1 public class Another { 2 private Callback callback; 3 //调用实现类的方法 4 public void setCallback(Callback callback) { 5 this.callback = callback; 6 } 7 //业务须要的时候,经过委派,来调用实现类的具体方法 8 public void doCallback(){ 9 System.out.println(callback.callBack()); 10 } 11 }
三、测试回调函数
1 public class TestCallcack { 2 public static void main(String[] args) { 3 //建立调用者的实现类 4 Another another = new Another(); 5 //将回掉接口注册到实现类中 6 another.setCallback(new Callback() { 7 @Override 8 public String callBack() { 9 return "you are a pig"; 10 } 11 }); 12 //执行回调函数 13 another.doCallback(); 14 } 15 }
回调方法的使用一般发生在“java接口”和“抽象类”的使用过程当中。模板方法设计模式就使用方法回调的机制,该模式首先定义特定的步骤的算法骨架,而将一些步骤延迟到子类中去实现的设计模式。模板方法设计模式使得子类能够不改变一个算法的结构便可从新定义该算法的某些特定步骤。
模板方式设计模式的适用性:
一、一次性实现一个算法的不变部分,并将可变的算法留给子类来实现。
二、各子类中公共的行为应该被提取出来并集中一个公共父类中以免代码重复。
三、能够控制子类扩展。
模板实例:
抽象模板方法类:
1 public abstract class AbstractSup { 2 //须要子类实现的方法 3 public abstract void print(); 4 //模板方法 5 public void doPrint(){ 6 System.out.println("执行模板方法"); 7 for (int i = 0; i < 3; i++) { 8 print(); 9 } 10 } 11 }
子类实现模板方式类:
1 public class SubClass extends AbstractSup{ 2 @Override 3 public void print() { 4 System.out.println("子类的实现方法"); 5 } 6 7 }
模板方法测试类:
1 public class TempleteTest { 2 public static void main(String[] args) { 3 SubClass subClass = new SubClass(); 4 subClass.print(); 5 subClass.doPrint(); 6 } 7 }
下面深刻介绍下spring模板方法的使用,以JdbcTemplete为例,详细说明模板模式和回调机制的使用。
首先看一下经典的JDBC编程的例子:
1 public List<User> query() { 2 3 List<User> userList = new ArrayList<User>(); 4 String sql = "select * from User"; 5 6 Connection con = null; 7 PreparedStatement pst = null; 8 ResultSet rs = null; 9 try { 10 con = HsqldbUtil.getConnection(); 11 pst = con.prepareStatement(sql); 12 rs = pst.executeQuery(); 13 14 User user = null; 15 while (rs.next()) { 16 17 user = new User(); 18 user.setId(rs.getInt("id")); 19 user.setUserName(rs.getString("user_name")); 20 user.setBirth(rs.getDate("birth")); 21 user.setCreateDate(rs.getDate("create_date")); 22 userList.add(user); 23 } 24 25 26 } catch (SQLException e) { 27 e.printStackTrace(); 28 }finally{ 29 if(rs != null){ 30 try { 31 rs.close(); 32 } catch (SQLException e) { 33 e.printStackTrace(); 34 } 35 } 36 try { 37 pst.close(); 38 } catch (SQLException e) { 39 e.printStackTrace(); 40 } 41 try { 42 if(!con.isClosed()){ 43 try { 44 con.close(); 45 } catch (SQLException e) { 46 e.printStackTrace(); 47 } 48 } 49 } catch (SQLException e) { 50 e.printStackTrace(); 51 } 52 53 } 54 return userList; 55 }
一个简单的查询,就要作这么一大堆事情,并且还要处理异常,咱们不防来梳理一下:
1、获取connection
2、获取statement
3、获取resultset
4、遍历resultset并封装成集合
5、依次关闭connection,statement,resultset,并且还要考虑各类异常等等。
若是是多个查询会产生较多的重复代码,这时候就可使用模板机制,经过观察咱们发现上面步骤中大多数都是重复的,可复用的,只有在遍历ResultSet并封装成集合的这一步骤是可定制的,由于每张表都映射不一样的java bean。这部分代码是没有办法复用的,只能定制。
抽象类代码:
1 public abstract class JdbcTemplate { 2 3 //模板方法 4 public final Object execute(String sql) throws SQLException{ 5 6 Connection con = HsqldbUtil.getConnection(); 7 Statement stmt = null; 8 try { 9 10 stmt = con.createStatement(); 11 ResultSet rs = stmt.executeQuery(sql); 12 Object result = doInStatement(rs);//抽象方法(定制方法,须要子类实现) 13 return result; 14 } 15 catch (SQLException ex) { 16 ex.printStackTrace(); 17 throw ex; 18 } 19 finally { 20 21 try { 22 stmt.close(); 23 } catch (SQLException e) { 24 e.printStackTrace(); 25 } 26 try { 27 if(!con.isClosed()){ 28 try { 29 con.close(); 30 } catch (SQLException e) { 31 e.printStackTrace(); 32 } 33 } 34 } catch (SQLException e) { 35 e.printStackTrace(); 36 } 37 38 } 39 } 40 41 //抽象方法(定制方法) 42 protected abstract Object doInStatement(ResultSet rs); 43 }
这个抽象类中,封装了SUN JDBC API的主要流程,而遍历ResultSet这一步骤则放到抽象方法doInStatement()中,由子类负责实现。
子类实现代码:
1 public class JdbcTemplateUserImpl extends JdbcTemplate { 2 3 @Override 4 protected Object doInStatement(ResultSet rs) { 5 List<User> userList = new ArrayList<User>(); 6 7 try { 8 User user = null; 9 while (rs.next()) { 10 11 user = new User(); 12 user.setId(rs.getInt("id")); 13 user.setUserName(rs.getString("user_name")); 14 user.setBirth(rs.getDate("birth")); 15 user.setCreateDate(rs.getDate("create_date")); 16 userList.add(user); 17 } 18 return userList; 19 } catch (SQLException e) { 20 e.printStackTrace(); 21 return null; 22 } 23 } 24 25 }
咱们在doInStatement()方法中,对ResultSet进行了遍历,最后并返回。
测试代码:
1 String sql = "select * from User"; 2 JdbcTemplate jt = new JdbcTemplateUserImpl(); 3 List<User> userList = (List<User>) jt.execute(sql);
模板机制的使用到此为止,可是若是每次调用jdbcTemplate时,都要继承一下上面的父类,这样挺不方便的,这样回调机制就能够发挥做用了。
所谓回调,就是方法参数中传递一个接口,父类在调用此方法时,必须调用方法中传递的接口的实现类。
回调加模板模式实现
回调接口:
1 public interface StatementCallback { 2 Object doInStatement(Statement stmt) throws SQLException; 3 }
模板方法:
1 public class JdbcTemplate { 2 3 //模板方法 4 public final Object execute(StatementCallback action) throws SQLException{ 5 6 Connection con = HsqldbUtil.getConnection(); 7 Statement stmt = null; 8 try { 9 10 stmt = con.createStatement(); 11 Object result = action.doInStatement(rs);//回调方法 12 return result; 13 } 14 catch (SQLException ex) { 15 ex.printStackTrace(); 16 throw ex; 17 } 18 finally { 19 20 try { 21 stmt.close(); 22 } catch (SQLException e) { 23 e.printStackTrace(); 24 } 25 try { 26 if(!con.isClosed()){ 27 try { 28 con.close(); 29 } catch (SQLException e) { 30 e.printStackTrace(); 31 } 32 } 33 } catch (SQLException e) { 34 e.printStackTrace(); 35 } 36 37 } 38 } 39 } 40 public Object query(StatementCallback stmt) throws SQLException{ 41 return execute(stmt); 42 } 43 }
测试的类:
1 public Object query(final String sql) throws SQLException { 2 class QueryStatementCallback implements StatementCallback { 3 4 public Object doInStatement(Statement stmt) throws SQLException { 5 ResultSet rs = stmt.executeQuery(sql); 6 List<User> userList = new ArrayList<User>(); 7 8 User user = null; 9 while (rs.next()) { 10 11 user = new User(); 12 user.setId(rs.getInt("id")); 13 user.setUserName(rs.getString("user_name")); 14 user.setBirth(rs.getDate("birth")); 15 user.setCreateDate(rs.getDate("create_date")); 16 userList.add(user); 17 } 18 return userList; 19 20 } 21 22 } 23 24 JdbcTemplate jt = new JdbcTemplate(); 25 return jt.query(new QueryStatementCallback()); 26 }
为何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和转换异常等。