前言

作为一名开发人员,你是否常常看到上面这张图中的场景?

那你在看到这种图的时候是否会有这样的纳闷:为什么这个 res 对象不是 null, 是谁给你赋的值呢,这个命名是我掉的办法啊?我没有在这边给它传值啊?上面咱们就来以前端 axios 组件为引,深入探讨一下回调办法。

什么是回调

在计算机程序设计中,回调:是指通过函数参数将某一块可执行代码的援用传递到其余函数,供其余函数调用的编程思维。

其目标是容许 底层代码 调用在 高层定义的子程序 。

艰深的来讲就是:A 类中调用 B 类中的某个办法 C,而后 B 类中办法 C 在反过来调用 A 类中的办法 D,在这外面 D 就是回调办法。

代码示例:

package com.aha.commons.callback;import lombok.Data;import lombok.experimental.Accessors;import lombok.extern.slf4j.Slf4j;import java.util.HashMap;import java.util.Map;/** * 模仿前端调用接口组件来解说回调的过程 * * @author WT * date 2021/11/10 */interface CallbackInterface {    // 近程调用接口之后回调的办法    void callback(RPCResponse rpcResponse);}/** * 调用接口之后返回的对象 */@Data@Accessors(chain = true)class RPCResponse {    private Integer code;    private String message;    private Map<String, Object> data;}/** * 近程调用组件 */@Slf4jpublic class RPCModule {    public void call (String url, Map<String,String> params, CallbackInterface callbackInterface) {        log.info("近程调用接口地址为:{},参数为:{}",url,params);        log.info("解决近程调用接口响应数据封装响应体...");        HashMap<String, Object> data = new HashMap<>();        data.put("name", "aha");        data.put("age", 24);        RPCResponse response = new RPCResponse().setCode(200).setMessage("申请接口胜利").setData(data);        log.info("调用接口实现,执行回调办法...");        // 重点: 这边调用的是子类实现的 callback 办法        callbackInterface.callback(response);    }}@Slf4jclass CustomFrontPage {    public static void main(String[] args) {        HashMap<String, String> params = new HashMap<>();        params.put("name", "aha");        // 模仿前端近程调用的过程 - 查问名字叫 aha 的用户        new RPCModule().call("/api/users", params, res -> {            // 重点: 这边是 RPCModule 调用上游获取响应信息之后, 回调这个办法的, res 之所以有值是 RPCModule 调用时传递过去的            if (res.getCode().equals(200)) {                log.info("申请后端接口胜利,执行胜利回调,取得的响应信息为:{},取得的响应数据为:{}", res.getMessage(), res.getData());            } else {                log.error("申请后端接口胜利,执行失败回调,谬误的响应码为:{}", res.getCode());            }        });    }}

过程解说:下面的示例是模仿了前端应用 Ajax 调用后端接口的过程。

关注 CustomFrontPage 类中的 main 办法,此办法的作用便是构建申请参数,发动近程调用接口。这个过程便是 CustomFrontPage 类的 main 去调用 RPCModulecall 办法,而 RPCModulecall 办法执行之后回调 CustomFrontPage 类中匿名外部类重写的 callback 办法的过程。

在这个过程中,RPCModule 就是底层代码,CustomFrontPage 类中匿名外部类重写的 callback 办法 就是高层定义的子程序。

实现回调的外围便是子类对象援用的传递。下面示例中应用的是匿名外部类的形式将援用传递给底层代码了。

回调的分类

  1. 同步回调:阻塞以后线程,回调办法执行完之后,继续执行上面的代码。
  2. 异步回调:不阻塞以后线程,能够继续执行上面的代码。

同步回调的实现形式就是下面那种形式,这边不在进行赘述,其实个别近程申请接口都应该是异步的形式。

异步回调的实现

异步回调的话,最容易想到的计划便是多线程的形式了。

package com.aha.commons.callback;import lombok.extern.slf4j.Slf4j;import org.springframework.util.ObjectUtils;import java.util.HashMap;import java.util.Map;/** * 近程调用组件 */@Slf4jpublic class ASyncRPCModule {    public void call (String url, Map<String,String> params, CallbackInterface callbackInterface) {        log.info("近程调用接口地址为:{},参数为:{}",url,params);        log.info("解决近程调用接口响应数据封装响应体...");        HashMap<String, Object> data = new HashMap<>();        data.put("name", "aha");        data.put("age", 24);        RPCResponse response = new RPCResponse().setCode(200).setMessage("申请接口胜利").setData(data);        log.info("调用接口实现,执行回调办法...");        // 重点: 这边调用的是子类实现的 callback 办法        callbackInterface.callback(response);            }}@Slf4jclass ASyncCustomFrontPage {    public static void main(String[] args) {        HashMap<String, String> params = new HashMap<>();        params.put("name", "aha");        // 异步回调 -> 最简略的形式便是通过多线程的形式        new Thread(() ->            // 模仿前端近程调用的过程 - 查问名字叫 aha 的用户            new ASyncRPCModule().call("/api/users", params, res -> {                if (ObjectUtils.isEmpty(res) || ObjectUtils.isEmpty(res.getCode())) {                    log.error("申请后端接口异样, 响应对象为 null");                    return;                }                // 重点: 这边是 RPCModule 调用上游获取响应信息之后, 回调这个办法的, res 之所以有值是 RPCModule 调用时传递过去的                if (res.getCode().equals(200)) {                    log.info("申请后端接口胜利,执行胜利回调,取得的响应信息为:{},取得的响应数据为:{}", res.getMessage(), res.getData());                } else {                    log.error("申请后端接口胜利,执行失败回调,谬误的响应码为:{}", res.getCode());                }            })        ).start();        log.info("因为是异步执行,主线程没有阻塞,我在调用接口的上面,然而仍然可能是我先执行");    }}