轉(zhuǎn)載自Java回調(diào)機制解析
模塊之間總是存在這一定的接口,從調(diào)用方式上看,可以分為三類:同步調(diào)用、回調(diào)和異步調(diào)用。同步調(diào)用是一種阻塞式調(diào)用,也是我們在寫程序中經(jīng)常使用的;回調(diào)是一種雙向的調(diào)用模式,也就是說,被調(diào)用的接口被調(diào)用時也會調(diào)用對方的接口,這句話可能有點繞,等文章后面舉例說明;異步調(diào)用是一種類似消息或事件的機制,解決了同步阻塞的問題,舉例來講:A通知B后,他們各走各的路,互不影響,不用像同步調(diào)用那樣,A通知B后,非得等到B走完后,A才繼續(xù)走?;卣{(diào)是異步的基本,因此下面著重說回調(diào)機制。
我們暫且不討論回調(diào)的一些名詞和運行機制,首先說為什么會存在回調(diào)這樣一種調(diào)用?同步和異步機制的出現(xiàn)不必多說,大家心知肚明,那回調(diào)機制為什么會出現(xiàn)呢?在我們現(xiàn)實生活中,有如下這樣場景:有一位老板(上層模塊)很忙,他沒有時間盯著員工(下層模塊)干活,然后他告訴自己的雇員,干完當(dāng)前這些事情后,告訴他干活的結(jié)果。這個例子其實是一個回調(diào)+異步的例子,再舉一個例子,A程序員寫了一段程序a,其中預(yù)留了回調(diào)函數(shù)接口,并封裝好了該程序,程序員B讓a調(diào)用自己的程序b中的一個方法,于是,他通過a中的接口回調(diào)自己b中的方法,到這里你可能似懂非懂了,后面會繼續(xù)說明回調(diào)的出現(xiàn)原因。接下來我們把上面例子變成代碼,看到網(wǎng)上很多人最后搞混了異步和回調(diào),因此例子中不加入異步調(diào)用。(注意:回調(diào)可不是解決什么調(diào)用時間過長問題,那是異步!)
首先創(chuàng)建一個回調(diào)接口,讓老板得告知干完活如何找到他的方式:留下老板辦公室地址:
package net.easyway.test;
/**
* 此接口為聯(lián)系的方式,不論是電話號碼還是聯(lián)系地址,作為
* 老板都必須要實現(xiàn)此接口
* @author Administrator
*
*/
public interface CallBackInterface {
public void execute();
} ```
創(chuàng)建回調(diào)對象,就是老板本人,因為員工干完活后要給他打電話,因此老板必須實現(xiàn)回調(diào)接口,不然員工去哪里找老板?
package net.easyway.test;
/**
- 老板是作為上層應(yīng)用身份出現(xiàn)的,下層應(yīng)用(員工)是不知道
- 有哪些方法,因此他想被下層應(yīng)用(員工)調(diào)用必須實現(xiàn)此接口
- @author Administrator
*/
public class Boss implements CallBackInterface {
@Override
public void execute() {
System.out.println("收到了??!" + System.currentTimeMillis());
}
}
創(chuàng)建控制類,也就是員工對象,他必須持有老板的地址(回調(diào)接口),即使老板換了一茬又一茬,辦公室不變,總能找到對應(yīng)的老板。
package net.easyway.test;
/**
- 員工類,必須要記住,這是一個底層類,底層是不了解上層服務(wù)的
- @author Administrator
*/
public class Employee {
private CallBackInterface callBack = null;
//告訴老板的聯(lián)系方式,也就是注冊
public void setCallBack(CallBackInterface callBack){
this.callBack = callBack;
}
//工人干活
public void doSome(){
//1.開始干活了
for(int i=0;i<10;i++){
System.out.println("第【" + i + "】事情干完了!");
}
//2.告訴老板干完了
callBack.execute();
}
}
[測試](http://lib.csdn.net/base/softwaretest)類代碼:
package net.easyway.test;
public class Client {
public static void main(String[] args) {
Employee emp = new Employee();
//將回調(diào)對象(上層對象)傳入,注冊
emp.setCallBack(new Boss());
//開啟控制器對象運行
emp.doSome();
}
}
上面這個例子,大家可以和程序員A和程序員B的那個例子結(jié)合對照下。
看了上面的例子,有的人可能認(rèn)為,這不是面向接口的編程嗎?怎么會是回調(diào),你再好好想想,咱們面向接口的編程的調(diào)用關(guān)系?在三層中,當(dāng)業(yè)務(wù)層調(diào)用數(shù)據(jù)層時,是不需要把業(yè)務(wù)層自身傳遞到數(shù)據(jù)層的,并且這是一種上層調(diào)用下層的關(guān)系,比如我們在用框架的時候,一般直接調(diào)用框架提供的API就可以了,但回調(diào)不同,當(dāng)框架不能滿足需求,我們想讓框架來調(diào)用自己的類方法,怎么做呢?總不至于去修改框架吧。許多優(yōu)秀的框架提幾乎都供了相關(guān)的接口,我們只需要實現(xiàn)相關(guān)接口,即可完成了注冊,然后在合適的時候讓框架來調(diào)用我們自己的類,還記不記得我們在使用Struts時,當(dāng)我們編寫Action時,就需要繼承Action類,然后實現(xiàn)execute()方法,在execute()方法中寫咱們自己的業(yè)務(wù)邏輯代碼,完成對用戶請求的處理。由此可以猜測,框架和容器中會提供大量的回調(diào)接口,以滿足個性化的定制。
不知道上面這個例子懂了沒有?我們現(xiàn)在可以想象Filter和Interceptor的區(qū)別了,這兩者其中最大的一個區(qū)別是Filter是基于回調(diào)函數(shù),需要容器的支持,沒有容器是無法回調(diào)doFilter()方法,而Interceptor是基于[Java](http://lib.csdn.net/base/javaee)的反射機制的,和容器無關(guān)。那到此是否又將反射和回調(diào)搞混了呢?請見我講Java動態(tài)代理的博客《[以此之長,補彼之短](http://blog.csdn.net/bjyfb/article/details/7350256)[----AOP(代理模式)](http://blog.csdn.net/bjyfb/article/details/7350256)》。
總之,要明確的一點是,首先要搞清回調(diào)函數(shù)出現(xiàn)的原因,也就是適用場景,才能搞清楚回調(diào)機制,不然事倍功半。
最后,再舉一例,為了使我們寫的函數(shù)接近完美,就把一部分功能外包給別人,讓別人個性化定制,至于別人怎么實現(xiàn)不管,我唯一要做的就是定義好相關(guān)接口,這一設(shè)計允許了底層代碼調(diào)用高層定義的子程序,增強程序靈活性,和反射有著異曲同工之妙,這才是回調(diào)的真正原因!
用一段話來總結(jié)下回調(diào):上層模塊封裝時,很難預(yù)料下層模塊會如何實現(xiàn),因此,上層模塊只需定義好自己需要但不能預(yù)料的接口(也就是回調(diào)接口),當(dāng)下層模塊調(diào)用上層模塊時,根據(jù)當(dāng)前需要的實現(xiàn)回調(diào)接口,并通過注冊或參數(shù)方式傳入上層模塊即可,這樣就實現(xiàn)下層調(diào)用上層,并且上層還能根據(jù)傳入的引用來調(diào)用下層的具體實現(xiàn),將程序的靈活性大大的增加了。本打算以[spring](http://lib.csdn.net/base/javaee) jdbctemplate作為實例,但里面涉及到不止回調(diào),還有模板模式等,怕理解困難就沒有采用,感興趣的讀者可自行查看相關(guān)源碼,也是回調(diào)+內(nèi)部類的很好示例。