回调+异步任务处理

回调+异步任务处理

  • 模拟 “客户端发消息给服务端,服务端处理后(延迟 5 秒)回调通知客户端” 的流程
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
75
76
import java.lang.Thread;

// 1. 定义回调接口:约定服务端处理完后要调用的方法
interface CSCallback {
// 服务端处理完成后,回调客户端的这个方法传递状态
void process(String status);
}

// 2. 服务端类:接收消息、处理、回调客户端
class Server {
// 方法参数用接口CSCallback:接收“实现了该接口的客户端对象”
public void getClientMsg(CSCallback csCallback, String msg) {
// 打印服务端接收消息的日志
System.out.println("服务端:收到客户端消息 -> [" + msg + "],开始处理(预计5秒)...");

try {
// 模拟服务端处理数据的耗时(阻塞当前线程5秒)
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

// 处理完成,准备回调客户端
System.out.println("服务端:数据处理完成,准备返回状态");
String resultStatus = "200(成功)";
// 调用“客户端实现的process方法”:实际执行的是Client类的process
csCallback.process(resultStatus);
}
}

// 3. 客户端类:实现回调接口,发送消息给服务端
class Client implements CSCallback {
private Server server; // 客户端持有服务端的引用(用于发消息)

// 构造方法:初始化时关联服务端
public Client(Server server) {
this.server = server;
}

// 客户端发送消息的方法(异步)
public void sendMsg(final String msg) {
System.out.println("客户端:准备发送消息 -> [" + msg + "]");

// 启动新线程:让服务端的处理在新线程中执行,不阻塞客户端主线程
new Thread(new Runnable() {
public void run() {
// 把“客户端自己(实现了CSCallback)”和消息传给服务端
server.getClientMsg(Client.this, msg);
}
}).start();

// 主线程不用等待,直接继续执行
System.out.println("客户端:消息已异步发出,主线程可以做其他事");
}

// 实现回调接口的方法:服务端处理完后会调用这个方法
@Override
public void process(String status) {
System.out.println("客户端:收到服务端回调 -> 处理状态:" + status);
}
}

// 4. 测试类:启动程序
public class TestCallBack {
public static void main(String[] args) {
// 创建服务端实例
Server server = new Server();
// 创建客户端实例(关联服务端)
Client client = new Client(server);

// 客户端发送消息
client.sendMsg("Server, Hello! 我是客户端~");
// 验证主线程不阻塞:这行代码会在服务端处理时直接执行
System.out.println("测试类:主线程未被阻塞,继续执行其他逻辑");
}
}
  • 输出:
1
2
3
4
5
6
客户端:准备发送消息 -> [Server, Hello! 我是客户端~]
客户端:消息已异步发出,主线程可以做其他事
测试类:主线程未被阻塞,继续执行其他逻辑
服务端:收到客户端消息 -> [Server, Hello! 我是客户端~],开始处理(预计5秒)...
服务端:数据处理完成,准备返回状态
客户端:收到服务端回调 -> 处理状态:200(成功)

这个代码是Java 中 “回调模式” 实现异步任务处理的经典示例,核心是模拟 “客户端发消息给服务端,服务端处理后(延迟 5 秒)回调通知客户端” 的流程。我分模块拆解,帮你彻底搞懂~

一、核心概念先理清

  • 回调(Callback):A 调用 B 的方法时,把 A 自己的引用(或接口实现)传给 B;B 处理完后,调用 A 的方法通知结果(相当于 “反向调用”)。
  • 异步(Async):客户端发消息后不用等待服务端处理完成,继续做自己的事;服务端处理完后再 “回调” 客户端告知结果。

二、代码模块逐行解析

1. 回调接口 CSCallback

1
2
3
interface CSCallback { // 定义回调的“协议”:服务端处理完后要调用这个方法
public void process(String status);
}
  • 作用:约定服务端处理完成后,要给客户端返回什么格式的结果(这里是status字符串)。
  • 客户端需要实现这个接口,才能让服务端 “反向调用” 自己的方法。

2. 服务端类 Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Server { // 服务端:接收客户端消息、处理、回调通知
public void getClientMsg(CSCallback csCallback, String msg) {
// 1. 打印接收消息的日志
System.out.println("服务端:服务端接收到客户端发送的消息为 [" + msg + "], 经过5秒后...");

try {
Thread.sleep(5 * 1000); // 模拟服务端“处理数据需要5秒”(阻塞当前线程)
} catch (InterruptedException e) {
e.printStackTrace();
}

// 2. 处理完成后,准备回调客户端
System.out.println("服务端:数据处理成功,返回成功状态 [200]");
String status = "200";
csCallback.process(status); // 调用客户端实现的process方法(回调!)
}
}
  • 核心方法 getClientMsg
    • 参数 1 csCallback客户端的接口实现对象(因为客户端实现了CSCallback,所以能传进来)。
    • 参数 2 msg:客户端发送的消息内容。
    • 逻辑:接收消息 → 模拟处理延迟(5 秒) → 处理完成后,调用客户端的process方法(这就是 “回调” 的核心动作)。

3. 客户端类 Client(实现回调接口)

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
class Client implements CSCallback { // 客户端:实现回调接口,接收服务端的通知
private Server server; // 客户端持有服务端的引用(要给服务端发消息)

// 构造方法:初始化服务端对象
public Client(Server server) {
this.server = server;
}

// 客户端发送消息的方法
public void sendMsg(final String msg) {
System.out.println("客户端:发送的消息为 [" + msg + "]");

// 关键:创建新线程,异步调用服务端的getClientMsg
new Thread(new Runnable() {
public void run() {
// 把“客户端自己(this)”和消息传给服务端
server.getClientMsg(Client.this, msg);
}
}).start(); // 启动线程,异步执行

// 主线程不用等服务端处理,直接继续执行
System.out.println("客户端:异步发送成功");
}

// 实现回调接口的方法:服务端处理完后会调用这个方法
public void process(String status) {
System.out.println("客户端:服务端回调状态为 [" + status + "]");
}
}
  • 核心点:

    1. implements CSCallback:客户端必须实现回调接口,这样服务端才能调用它的process方法。

    2. sendMsg
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      方法:

      - 用`new Thread`包裹对服务端的调用 → **异步执行**(主线程不会被服务端的 5 秒延迟阻塞)。
      - `Client.this`:把 “当前客户端对象” 传给服务端(因为客户端实现了`CSCallback`,所以能作为参数传递)。

      3. `process`方法:**服务端处理完成后,会回调这个方法**,打印服务端返回的状态。

      #### 4. 测试类 `TestCallBack`

      ```java
      public class TestCallBack {
      public static void main(String[] args) {
      Server server = new Server(); // 创建服务端
      Client client = new Client(server); // 创建客户端(关联服务端)

      client.sendMsg("Server,Hello~"); // 客户端发消息
      System.out.println("异步处理不会阻塞主线程"); // 主线程继续执行
      }
      }
  • 执行流程:

    1. 创建服务端、客户端对象。
    2. 客户端调用sendMsg发消息 → 启动新线程调用服务端getClientMsg
    3. 主线程不用等,直接打印 “异步处理不会阻塞主线程”。
    4. 服务端处理 5 秒后,回调客户端的process方法,打印状态。

三、最终运行结果(顺序参考)

1
2
3
4
5
6
客户端:准备发送消息 -> [Server, Hello! 我是客户端~]
客户端:消息已异步发出,主线程可以做其他事
测试类:主线程未被阻塞,继续执行其他逻辑
服务端:收到客户端消息 -> [Server, Hello! 我是客户端~],开始处理(预计5秒)...
服务端:数据处理完成,准备返回状态
客户端:收到服务端回调 -> 处理状态:200(成功)

四、核心亮点总结

  1. 回调的本质:客户端把自己(接口实现)传给服务端,服务端处理完后 “反向调用” 客户端的方法。
  2. 异步的实现:用Thread包裹服务端调用,让服务端的处理在新线程中执行,主线程不阻塞
  3. 解耦:服务端只依赖CSCallback接口,不用关心具体是哪个客户端;客户端只依赖接口,不用关心服务端的具体实现。

问题1:public void getClientMsg(CSCallback csCallback, String msg) {为什么接口可以作为参数

问题2:csCallback.process(status);接口为什么会形成对象,还可以调用函数

要搞懂这两个问题,核心是理解 Java 接口的 “多态特性”“接口不能直接实例化,但可以指向实现类对象” 这两个关键点。我用通俗的语言 + 代码示例拆解,保证一看就懂。

问题 1:为什么接口可以作为方法参数?

先给结论:接口作为参数,本质是接收 “实现了该接口的类的对象” —— 这是 Java 多态的核心应用,目的是解耦(方法不依赖具体类,只依赖接口)。

举个生活例子

比如定义一个 “支付接口 Pay”,里面有pay()方法;然后有AlipayWechatPay两个类实现这个接口。

现在写一个 “收银台” 方法checkout(Pay pay),参数是 Pay 接口 —— 这个方法不需要关心用户用支付宝还是微信,只要传 “实现了 Pay 接口的对象” 就行。

回到代码层面解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 接口
interface CSCallback {
void process(String status);
}

// 实现类(客户端)
class Client implements CSCallback {
@Override
public void process(String status) {
System.out.println("回调状态:" + status);
}
}

// 服务端方法:参数是CSCallback接口
public void getClientMsg(CSCallback csCallback, String msg) {
// 逻辑...
}

// 调用时传参:传入“实现了CSCallback的Client对象”
Client client = new Client();
server.getClientMsg(client, "消息"); // ✅ 完全合法

底层原理

  1. Java 中 “接口作为参数”,并不是要求传 “接口本身的对象”(接口根本不能 new),而是要求传 “实现了该接口的类的实例”
  2. 这是多态的体现:CSCallback csCallback 这个参数,是一个 “引用变量”,它可以指向任何实现CSCallback接口的类的对象(比如 Client 对象)。
  3. 好处:服务端的getClientMsg方法不需要关心传进来的是哪个类(比如 Client1、Client2),只要这个类实现了CSCallback,就能调用接口里的process方法 —— 实现了 “面向接口编程”,解耦了服务端和具体的客户端类。

反例(为什么要这么设计?)

如果不用接口做参数,服务端方法就要写死接收 Client 类:

1
2
// 耦合度极高!如果换一个Client2类,这个方法就用不了了
public void getClientMsg(Client client, String msg) { ... }

而用接口做参数,不管是 Client、Client2,只要实现了 CSCallback,都能传给这个方法 —— 这就是接口作为参数的核心价值。

问题 2:csCallback.process (status); 接口为什么会形成对象,还可以调用函数?

先纠正一个认知:接口本身不会形成对象! 你看到的csCallback不是接口对象,而是 “实现了接口的类的对象”,只不过用接口类型的变量指向它而已。

分步拆解

第一步:csCallback变量的本质
1
2
// 服务端方法里的参数:接口类型的引用变量
CSCallback csCallback;

这个变量不是 “接口对象”,而是一个 “引用”(类似指针),它的作用是:指向任何实现了 CSCallback 接口的类的实例

第二步:调用方法时,传入的是 “实现类对象”
1
2
Client client = new Client(); // 创建Client对象(实现了CSCallback)
server.getClientMsg(client, "消息"); // 把Client对象传给csCallback参数

此时,方法内部的csCallback变量,指向的是Client类的实例(真实对象是 Client,不是 CSCallback 接口)。

第三步:调用csCallback.process(status)的底层逻辑
1
csCallback.process(status); // 实际调用的是Client类重写的process方法

因为csCallback指向的是 Client 对象,所以调用process方法时,会执行 Client 类中重写的process方法 —— 这是多态的 “动态绑定”:编译时看接口类型,运行时看实际指向的对象类型

用代码验证(最直观)

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
// 接口类型变量,指向实现类对象
CSCallback callback = new Client();

// 打印变量的真实类型:class Client(不是接口)
System.out.println(callback.getClass().getName());

// 调用process,实际执行Client的process方法
callback.process("200");
}
}

输出结果:

1
2
Client
回调状态:200

核心结论

  1. csCallback不是 “接口对象”,而是 “接口类型的引用变量”,它指向的是实现了该接口的类的对象(比如 Client 对象)。

  2. 能调用

    1
    process

    方法,是因为:

    • 接口中定义了process方法(约定了必须实现);
    • 实际指向的对象(Client)重写了这个方法;
    • 运行时会动态找到并执行 Client 的process方法。