一、什么是回调:
回调是一种双向的调用模式,程序模块之间通过这样的接口调用完成通信联系,回调的核心就是回调方将本身即this传递给调用方,这样调用方就可以在调用完毕之后再告诉回调方它想要知道的信息。
回调函数用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而下层在一定条件下触发回调,例如作为一个驱动,是一个底层,他在收到一个数据时,除了完成本层的处理工作外,还将进行回调,它将这个数据交给上层应用层来做进一步处理,这在分层的数据通信中很普遍。其实回调和API非常接近,他们的共性都是跨层调用的函数。但区别是API是低层提供给高层的调用,一般这个函数对高层都是已知的;而回调正好相反,他是高层提供给底层的调用,对于低层他是未知的,必须由高层进行安装。这个安装函数其实就是一个低层提供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来保存这个回调,在需要调用时,只需引用这个函数指针和相关的参数指针。
其实:回调就是该函数写在高层,低层通过一个函数指针保存这个函数,在某个事件的触发下,低层通过该函数指针调用高层那个函数。从调用方式上看,可以分为两类:同步回调、异步回调。
通俗来说:
就是A类需要调用B类中的方法,然后B类方法执行结束后通过函数指针(A类传过来)调用A类中的回调方法。
回调机制使用场景:
在项目中,在支付宝的沙箱支付中就用到了回调机制。在支付Controller层中通过pay方法调用支付宝提供的API,跳转支付宝支付界面。
但是在支付接口中我们并不是等待支付宝支付页面支付完成才继续走下去即非阻塞式,而且我们在pay方法中也并不知道支付宝支付页面中的支付结果具体如何。那么我们该如何获取结果呢,就是通过支付宝的异步回调接口来通知我们结果。
在支付完成后支付宝会异步回调Controller层中的回调方法来通知结果。
二、同步回调与异步回调:
1、同步回调:
同步调用是一种阻塞式调用,是最基本并且最简单的一种调用方式,类A的方法a()调用类B的方法b(),一直等待b()方法执行完毕,a()方法才能继续往下走。这种调用方式适用于方法b()执行时间不长的情况,因为b()方法执行时间一长或者直接阻塞的话,a()方法的余下代码是无法执行下去的,这样会造成整个流程的阻塞。
例如上方支付宝回调场景,加入是同步机制回调,那么在pay方法中我们就需要等待支付宝支付结果,那假设网络等因素导致支付结果很慢还没有出来,那么我们的程序就一直阻塞在这里,那这样性能就会非常糟糕。
2、异步回调:
(1)异步调用是为了解决同步调用可能出现阻塞,导致整个流程卡住而产生的一种调用方式。类A的方法方法a()通过新起线程的方式调用类B的方法b(),代码接着直接往下执行,这样无论方法b()执行时间多久,都不会阻塞住方法a()的执行。但是这种方式,由于方法a()不等待方法b()的执行完成,在方法a()需要方法b()执行结果的情况下,必须通过一定的方式对方法b()的执行结果进行监听。为了完成这点,就需要另开一个线程了。
(2)异步调用在应用程序框架中具有广泛的应用,并且特指多线程情况下。它同Windows的消息循环机制,消息响应,消息队列,事件驱动机制以及设计模式中的观察者模式等都是紧密相关的。 在单线程方式下,计算机是一台严格意义上的冯·诺依曼式机器,一段代码调用另一段代码时,只能采用同步调用,必须等待这段代码执行完返回结果后,调用方才能继续往下执行。有了多线程的支持,可以采用异步调用,调用方和被调方可以属于两个不同的线程,调用方启动被调方线程后,不等对方返回结果就继续执行后续代码。被调方执行完毕后,通过某种手段通知调用方:结果已经出来,请酌情处理。异步回调常见于请求服务器数据,当取到数据时,会进行回调(例如支付宝支付回调)。
三、异步回调例子(JAVA):
上面讲了那么多,其实所谓回调,就是A类中调用了B类的某个方法C,然后B类反过来调用A类的方法D,D这个方法就叫回调方法。
Class A实现接口CallBack callback——背景1
class A中包含一个class B的引用b ——背景2
class B有一个参数为callback的方法f(CallBack callback) ——背景3
A的对象a调用B的方法 f(CallBack callback) ——A类调用B类的某个方法 C
然后b就可以在f(CallBack callback)方法中调用A的方法 ——B类调用A类的某个方法D
/*** java 异步接口回调*/
public interface CallBack {/*** 这时一个回调接口* @param result*/public void resultNotify(String result);
}
/** 相当于A类 有任务处理 需要调用B类中的方法进行处理* @author cg* @version 1.0* @Date 2024/4/20 15:33*/
public class Chen implements CallBack{private Li li;public void setLi(Li li){this.li=li;}//调用Li类 进行任务处理public void ToTaskLi( String task){//另起线程模拟异步调用场景new Thread(new Runnable() {@Overridepublic void run() {li.doTask(Chen.this,task);}}).start();//将任务交由类li处理 Chen类继续往下执行别的任务 无需阻塞playGame();}public void playGame(){System.out.println("Chen玩去了.......");}//异步回调函数@Overridepublic void resultNotify(String result) {System.out.println("Chen的接口回调结果:"+result);}
}
/** 相等于任务处理类 处理完成后通知A类 并将结果告知* @author cg* @version 1.0* @Date 2024/4/20 15:33*/
public class Li {public void doTask(CallBack callBack,String task){System.out.println("Li接受到任务:"+task);//模拟处理任务for (int i = 0; i < 100000; i++) {}System.out.println("Li类任务处理完毕......");String result="任务处理成功";//调用chen类的回调函数callBack.resultNotify(result);}
}
/** 测试java异步回调* @author cg* @version 1.0* @Date 2024/4/20 15:43*/
public class TestYiBuHuiDiao {public static void main(String[] args) {Chen chen=new Chen();Li li = new Li();chen.setLi(li);String task="拿取参数任务";chen.ToTaskLi(task);}
}
结果:
代码解析:
在这段代码中,Chen类是CallBack接口的实现类,而调用Li类的doTask()方法中传递了一个Chen.this参数(this关键字的使用),Chen.this相当于类Chen的实例对象(和Chen chen一样)。Li.doTask(CallBack callback)中参数要求是CallBack类型,因为Chen类实现了CallBack接口,所以它的实例可以作为CallBack类型的参数传递。
为什么使用CallBack callback作为参数?
这里就涉及到Java的接口和多态。因为CallBack是一个接口,任何实现了该接口的类都必须实现接口中定义的所有方法。所以只需要传入一个CallBack类型的引用就可以,而在Li类中不需要关心这个类指向的是哪个类的实例(在这里是指向的Chen类实例,也可以是各种实现该接口的实例),这就是用到了多态的概念。
那么如果写成li.doTask(Chen chen)可以吗?
这种方式当然也可以,但如果还有Wang、Liu.....等各种实现了CallBack接口的类同样也要调用这个方法呢。那么我是不是就要在li中定义doTask(Wang wang)、doTask(Li li).....各种方法。所以这里就可以看到多态提供的好处了。我们只需要定义接口类型即可,具体传入哪个是哪个类型无需关心。因为它绝对是这个接口的实现类。