詳解java模板和回調(diào)機(jī)制
最近看spring的JDBCTemplete的模板方式調(diào)用時(shí),對(duì)模板和回調(diào)產(chǎn)生了濃厚興趣,查詢了一些資料,做一些總結(jié)。
回調(diào)函數(shù):
所謂回調(diào),就是客戶程序C調(diào)用服務(wù)程序S中的某個(gè)函數(shù)A,然后S又在某個(gè)時(shí)候反過來調(diào)用C中的某個(gè)函數(shù)B,對(duì)于C來說,這個(gè)B便叫做回調(diào)函數(shù)?;卣{(diào)函數(shù)只是一個(gè)功能片段,由用戶按照回調(diào)函數(shù)調(diào)用約定來實(shí)現(xiàn)的一個(gè)函數(shù)?;卣{(diào)函數(shù)是一個(gè)工作流的一部分,由工作流來決定函數(shù)的調(diào)用(回調(diào))時(shí)機(jī)。一般說來,C不會(huì)自己調(diào)用B,C提供B的目的就是讓S來調(diào)用它,而且是C不得不提供。由于S并不知道C提供的B姓甚名誰,所以S會(huì)約定B的接口規(guī)范(函數(shù)原型),然后由C提前通過S的一個(gè)函數(shù)R告訴S自己將要使用B函數(shù),這個(gè)過程稱為回調(diào)函數(shù)的注冊(cè),R稱為注冊(cè)函數(shù)。Web Service以及Java 的RMI都用到回調(diào)機(jī)制,可以訪問遠(yuǎn)程服務(wù)器程序?;卣{(diào)函數(shù)包含下面幾個(gè)特性:
1、屬于工作流的一個(gè)部分;
2、必須按照工作流指定的調(diào)用約定來申明(定義);
3、他的調(diào)用時(shí)機(jī)由工作流決定,回調(diào)函數(shù)的實(shí)現(xiàn)者不能直接調(diào)用回調(diào)函數(shù)來實(shí)現(xiàn)工作流的功能;
回調(diào)機(jī)制:
回調(diào)機(jī)制是一種常見的設(shè)計(jì)模型,他把工作流內(nèi)的某個(gè)功能,按照約定的接口暴露給外部使用者,為外部使用者提供數(shù)據(jù),或要求外部使用者提供數(shù)據(jù)。
java回調(diào)機(jī)制:
軟件模塊之間總是存在著一定的接口,從調(diào)用方式上,可以把他們分為三類:同步調(diào)用、回調(diào)和異步調(diào)用。
同步調(diào)用:一種阻塞式調(diào)用,調(diào)用方要等待對(duì)方執(zhí)行完畢才返回,它是一種單向調(diào)用;
回 調(diào):一種雙向調(diào)用模式,也就是說,被調(diào)用方在接口被調(diào)用時(shí)也會(huì)調(diào)用對(duì)方的接口;
異步調(diào)用:一種類似消息或事件的機(jī)制,不過它的調(diào)用方向剛好相反,接口的服務(wù)在收到某種訊息或發(fā)生某種事件時(shí),會(huì)主動(dòng)通知客戶方(即調(diào)用客戶方的接口)。
回調(diào)和異步調(diào)用的關(guān)系非常緊密:使用回調(diào)來實(shí)現(xiàn)異步消息的注冊(cè),通過異步調(diào)用來實(shí)現(xiàn)消息的通知。
回調(diào)實(shí)例
1、回調(diào)接口
public interface Callback { String callBack(); }
2、調(diào)用者
public class Another { private Callback callback; //調(diào)用實(shí)現(xiàn)類的方法 public void setCallback(Callback callback) { this.callback = callback; } //業(yè)務(wù)需要的時(shí)候,通過委派,來調(diào)用實(shí)現(xiàn)類的具體方法 public void doCallback(){ System.out.println(callback.callBack()); } }
3、測(cè)試回調(diào)函數(shù)
public class TestCallcack { public static void main(String[] args) { //創(chuàng)建調(diào)用者的實(shí)現(xiàn)類 Another another = new Another(); //將回掉接口注冊(cè)到實(shí)現(xiàn)類中 another.setCallback(new Callback() { @Override public String callBack() { return "you are a pig"; } }); //執(zhí)行回調(diào)函數(shù) another.doCallback(); } }
回調(diào)方法的使用通常發(fā)生在“java接口”和“抽象類”的使用過程中。模板方法設(shè)計(jì)模式就使用方法回調(diào)的機(jī)制,該模式首先定義特定的步驟的算法骨架,而將一些步驟延遲到子類中去實(shí)現(xiàn)的設(shè)計(jì)模式。模板方法設(shè)計(jì)模式使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重新定義該算法的某些特定步驟。
模板方式設(shè)計(jì)模式的適用性:
1、一次性實(shí)現(xiàn)一個(gè)算法的不變部分,并將可變的算法留給子類來實(shí)現(xiàn)。
2、各子類中公共的行為應(yīng)該被提取出來并集中一個(gè)公共父類中以避免代碼重復(fù)。
3、可以控制子類擴(kuò)展。
模板實(shí)例:
抽象模板方法類:
public abstract class AbstractSup { //需要子類實(shí)現(xiàn)的方法 public abstract void print(); //模板方法 public void doPrint(){ System.out.println("執(zhí)行模板方法"); for (int i = 0; i < 3; i++) { print(); } } }
子類實(shí)現(xiàn)模板方式類:
public class SubClass extends AbstractSup{ @Override public void print() { System.out.println("子類的實(shí)現(xiàn)方法"); } }
模板方法測(cè)試類:
public class TempleteTest { public static void main(String[] args) { SubClass subClass = new SubClass(); subClass.print(); subClass.doPrint(); } }
下面深入介紹下spring模板方法的使用,以JdbcTemplete為例,詳細(xì)說明模板模式和回調(diào)機(jī)制的使用。
首先看一下經(jīng)典的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; }
一個(gè)簡(jiǎn)單的查詢,就要做這么一大堆事情,而且還要處理異常,我們不防來梳理一下:
1、獲取connection
2、獲取statement
3、獲取resultset
4、遍歷resultset并封裝成集合
5、依次關(guān)閉connection,statement,resultset,而且還要考慮各種異常等等。
如果是多個(gè)查詢會(huì)產(chǎn)生較多的重復(fù)代碼,這時(shí)候就可以使用模板機(jī)制,通過觀察我們發(fā)現(xiàn)上面步驟中大多數(shù)都是重復(fù)的,可復(fù)用的,只有在遍歷ResultSet并封裝成集合的這一步驟是可定制的,因?yàn)槊繌埍矶加成洳煌膉ava bean。這部分代碼是沒有辦法復(fù)用的,只能定制。
抽象類代碼:
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);//抽象方法(定制方法,需要子類實(shí)現(xiàn)) 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); }
這個(gè)抽象類中,封裝了SUN JDBC API的主要流程,而遍歷ResultSet這一步驟則放到抽象方法doInStatement()中,由子類負(fù)責(zé)實(shí)現(xiàn)。
子類實(shí)現(xiàn)代碼:
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; } } }
我們?cè)赿oInStatement()方法中,對(duì)ResultSet進(jìn)行了遍歷,最后并返回。
測(cè)試代碼:
String sql = "select * from User"; JdbcTemplate jt = new JdbcTemplateUserImpl(); List<User> userList = (List<User>) jt.execute(sql);
模板機(jī)制的使用到此為止,但是如果每次調(diào)用jdbcTemplate時(shí),都要繼承一下上面的父類,這樣挺不方便的,這樣回調(diào)機(jī)制就可以發(fā)揮作用了。
所謂回調(diào),就是方法參數(shù)中傳遞一個(gè)接口,父類在調(diào)用此方法時(shí),必須調(diào)用方法中傳遞的接口的實(shí)現(xiàn)類。
回調(diào)加模板模式實(shí)現(xiàn)
回調(diào)接口:
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);//回調(diào)方法 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); } }
測(cè)試的類:
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不用傳統(tǒng)的模板方法,而加之以Callback進(jìn)行配合呢?
試想,如果父類中有10個(gè)抽象方法,而繼承它的所有子類則要將這10個(gè)抽象方法全部實(shí)現(xiàn),子類顯得非常臃腫。而有時(shí)候某個(gè)子類只需要定制父類中的某一個(gè)方法該怎么辦呢?這個(gè)時(shí)候就要用到Callback回調(diào)了。
另外,上面這種方式基本上實(shí)現(xiàn)了模板方法+回調(diào)模式。但離spring的jdbcTemplate還有些距離。 我們上面雖然實(shí)現(xiàn)了模板方法+回調(diào)模式,但相對(duì)于Spring的JdbcTemplate則顯得有些“丑陋”。Spring引入了RowMapper和ResultSetExtractor的概念。 RowMapper接口負(fù)責(zé)處理某一行的數(shù)據(jù),例如,我們可以在mapRow方法里對(duì)某一行記錄進(jìn)行操作,或封裝成entity。 ResultSetExtractor是數(shù)據(jù)集抽取器,負(fù)責(zé)遍歷ResultSet并根據(jù)RowMapper里的規(guī)則對(duì)數(shù)據(jù)進(jìn)行處理。 RowMapper和ResultSetExtractor區(qū)別是,RowMapper是處理某一行數(shù)據(jù),返回一個(gè)實(shí)體對(duì)象。而ResultSetExtractor是處理一個(gè)數(shù)據(jù)集合,返回一個(gè)對(duì)象集合。
當(dāng)然,上面所述僅僅是Spring JdbcTemplte實(shí)現(xiàn)的基本原理,Spring JdbcTemplate內(nèi)部還做了更多的事情,比如,把所有的基本操作都封裝到JdbcOperations接口內(nèi),以及采用JdbcAccessor來管理DataSource和轉(zhuǎn)換異常等。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Java進(jìn)階核心之InputStream流深入講解
這篇文章主要給大家介紹了關(guān)于Java進(jìn)階核心之InputStream流的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02java mybatis框架實(shí)現(xiàn)多表關(guān)系查詢功能
這篇文章主要介紹了java mybatis框架實(shí)現(xiàn)多表關(guān)系查詢,基于Maven框架的整體設(shè)計(jì) —— 一多一的關(guān)系,文中通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10IDEA 2020.1 for Mac 下載安裝配置及出現(xiàn)的問題小結(jié)
這篇文章主要介紹了IDEA 2020.1 for Mac 下載安裝配置及出現(xiàn)的問題小結(jié),本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03Nacos源碼之注冊(cè)中心的實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Nacos源碼之注冊(cè)中心的實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Mybatis使用collection標(biāo)簽進(jìn)行樹形結(jié)構(gòu)數(shù)據(jù)查詢時(shí)攜帶外部參數(shù)查詢
這篇文章主要介紹了Mybatis使用collection標(biāo)簽進(jìn)行樹形結(jié)構(gòu)數(shù)據(jù)查詢時(shí)攜帶外部參數(shù)查詢,需要的朋友可以參考下2023-10-10