回调方法探索

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

前言

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

image.png

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

什么是回调

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

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

通俗的来讲就是:A 类中调用 B 类中的某个方法 C,然后 B 类中方法 C 在反过来调用 A 类中的方法 D,在这里面 D 就是回调方法。

代码示例:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Java复制代码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;

}

/**
* 远程调用组件
*/
@Slf4j
public 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);
}

}

@Slf4j
class 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. 异步回调:不阻塞当前线程,可以继续执行下面的代码。

同步回调的实现方式就是上面那种方式,这边不在进行赘述,其实一般远程请求接口都应该是异步的方式。

异步回调的实现

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

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
56
57
58
59
60
61
62
Java复制代码package com.aha.commons.callback;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;

import java.util.HashMap;
import java.util.Map;


/**
* 远程调用组件
*/
@Slf4j
public 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);

}

}

@Slf4j
class 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("因为是异步执行,主线程没有阻塞,我在调用接口的下面,但是依然可能是我先执行");

}

}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%