最近看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、回调接口
1 public interface Callback {2 String callBack();3 }
2、调用者
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 }
3、测试回调函数
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、一次性实现一个算法的不变部分,并将可变的算法留给子类来实现。
2、各子类中公共的行为应该被提取出来并集中一个公共父类中以避免代码重复。
3、可以控制子类扩展。
模板实例:
抽象模板方法类:
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 @Override3 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和转换异常等。