最近看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
2
3
4
|
public interface Callback { String callBack(); } |
2、調用者
1
2
3
4
5
6
7
8
9
10
11
|
public class Another { private Callback callback; //調用實現類的方法 public void setCallback(Callback callback) { this .callback = callback; } //業務需要的時候,通過委派,來調用實現類的具體方法 public void doCallback(){ System.out.println(callback.callBack()); } } |
3、測試回調函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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、可以控制子類擴展。
模板實例:
抽象模板方法類:
1
2
3
4
5
6
7
8
9
10
11
|
public abstract class AbstractSup { //需要子類實現的方法 public abstract void print(); //模板方法 public void doPrint(){ System.out.println( "執行模板方法" ); for ( int i = 0 ; i < 3 ; i++) { print(); } } } |
子類實現模板方式類:
1
2
3
4
5
6
7
|
public class SubClass extends AbstractSup{ @Override public void print() { System.out.println( "子類的實現方法" ); } } |
模板方法測試類:
1
2
3
4
5
6
7
|
public class TempleteTest { public static void main(String[] args) { SubClass subClass = new SubClass(); subClass.print(); subClass.doPrint(); } } |
下面深入介紹下spring模板方法的使用,以JdbcTemplete為例,詳細說明模板模式和回調機制的使用。
首先看一下經典的JDBC編程的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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。這部分代碼是沒有辦法復用的,只能定制。
抽象類代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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()中,由子類負責實現。
子類實現代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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進行了遍歷,最后并返回。
測試代碼:
1
2
3
|
String sql = "select * from User" ; JdbcTemplate jt = new JdbcTemplateUserImpl(); List<User> userList = (List<User>) jt.execute(sql); |
模板機制的使用到此為止,但是如果每次調用jdbcTemplate時,都要繼承一下上面的父類,這樣挺不方便的,這樣回調機制就可以發揮作用了。
所謂回調,就是方法參數中傳遞一個接口,父類在調用此方法時,必須調用方法中傳遞的接口的實現類。
回調加模板模式實現
回調接口:
1
2
3
|
public interface StatementCallback { Object doInStatement(Statement stmt) throws SQLException; } |
模板方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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); } } |
測試的類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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和轉換異常等。
以上就是本文的全部內容,希望對大家的學習有所幫助。