最近看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和轉換異常等。