最近看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和轉換異常等。
以上就是本文的全部內容,希望對大家的學習有所協助。