定义
天天说回调回调,真的了解吗?
消息异步为什么要传入回调接口,真的了解了吗?
前端 axios 发送请求写的箭头函数全都是回调函数,真的理解了吗?
其实回调很简单,就是比如 A 中的方法 a1() 调用了 B 中的方法 b1(),然后 b1() 方法执行完后(一般是执行完)再调用 a1() 方法传入的回调接口的回调方法将结果返回。回调方法一般都定义在接口当中,通用性更强。
那么肯定会有人说,直接通过 b1() 方法返回不就行了,为啥还要去搞什么回调去返回,这不脱裤子放屁?
其实不尽然,如果 a1() 方法调用 b1() 方法,b1() 方法阻塞了,一直重试,那么 a1() 方法也就无法继续向下执行,这种情况不是我们想要见到的。
一般使用回调方法的场景就是调用的方法十分耗时,或者追求效率,不太在意调用方法的结果。因此调用方不会去等,采用异步调用的方式,然后将回调接口通过方法传给被调用方。被调用操作执行完后,再调用接口中的回调方法通知调用方。
多说无益,举几个例子。
举例
场景一
现在有这么一个场景:老师上课提问学生,学生一时半会想不出来,老师不会一直等,而是转而问其他同学其他问题。随后该学生想出来了,告诉老师结果。
现在来编码实现一下。
先定义一个回调接口 CallBack
/**
* @author zxb 2022/9/23 20:43
*/
public interface CallBack {
/**
* 回调接口
* @param answer 收到的答案
*/
public void receive(String answer);
}
再定义一个 Teacher 类,teacher 类需要关联 student 类,因为要调用 student 的方法。并且调用时开启了一个新的线程,体现了异步调用。
package com.zxb.callback;
import lombok.extern.slf4j.Slf4j;
/**
* @author zxb 2022/9/24 8:49
*/
@Slf4j
public class Teacher implements CallBack {
private Student student;
public Teacher() {
}
public Teacher(Student student) {
this.student = student;
}
public void askQuestion(String question, CallBack callBack) {
log.info("听好了,我的问题是:" + question);
new Thread(() -> student.thinkQuestion(question, callBack)).start();
try {
// 这个睡眠只是为了日志打印更好看
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("那你先想,我问其他同学问题。");
}
@Override
public void receive(String answer) {
if ("2".equals(answer)) {
log.info("答对了");
} else {
log.info("答错了");
}
}
}
最后定义一个 Student 类,这里模拟学生想了 5 秒,然后调用 Teacher 传入的回调接口的回调方法告诉老师答案。
package com.zxb.callback;
import lombok.extern.slf4j.Slf4j;
/**
* @author zxb 2022/9/24 8:49
*/
@Slf4j
public class Student {
public void thinkQuestion(String question, CallBack callback) {
log.info("老师" + question + "有点难,我先站着想想。");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("老师我想到了");
callback.receive("2");
}
}
测试
public class CallbackTest {
public static void main(String[] args) {
Student student = new Student();
Teacher teacher = new Teacher(student);
teacher.askQuestion("1 + 1 等于几", teacher);
}
}
分析
这里老师问学生问题,学生要想很久,老师不想影响上课进度,故让学生自己先想,然后老师继续问其他同学问题,但是老师需要留一个口子给该学生,能让该学生通知自己,这是基于代码方面的思想。而这个留给学生通知自己的方式就是回调接口中的回调方法。
在这个例子中,Teacher 作为调用方自身实现了回调接口,重写了回调方法,老师实现了自己的逻辑,传给同学的回调接口实际上就是自己本身,然后学生调用老师重写的回调方法得以通知老师。
而调用方也可以不实现回调接口,这个回调接口由用户自己指定,比如场景二。
场景二
现在又有个场景,生产者发送消息,消息存放在消息中心,并且为了提高生产效率,生产者发完消息也先不管发送成功还是失败,而是继续向下走其他逻辑,只是给消息中心提供一个回调接口用于告知自己发送消息的结果。
那么现在来编码实现下。先定义一个回调接口。
package com.zxb.callback;
/**
* @author zxb 2022/9/24 17:19
*/
public interface SendCallback {
/**
* 发送成功
* @param sendResult 结果
*/
public void onSuccess(SendResult sendResult);
/**
* 发送失败
* @param exception 失败信息
*/
public void onFail(Exception exception);
}
再定义一个生产者。
package com.zxb.callback;
import lombok.extern.slf4j.Slf4j;
/**
* @author zxb 2022/9/24 17:31
*/
@Slf4j
public class Producer {
final private MsgCenter msgCenter = new MsgCenter();
public void send(String msg, SendCallback callback) {
log.info("开始发送消息");
new Thread(() ->{msgCenter.receiveMsg(msg, callback);}).start();
log.info("执行其他逻辑");
}
}
定义消息中心。
package com.zxb.callback;
import java.util.Date;
/**
* @author zxb 2022/9/24 17:39
*/
public class MsgCenter {
public void receiveMsg(String msg, SendCallback sendCallback){
try {
// 模拟延迟
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (msg == null || msg.length() == 0) {
sendCallback.onFail(new Exception("消息不能为空"));
} else if (msg.contains("窝草")) {
sendCallback.onFail(new Exception("此条消息被和谐"));
} else {
SendResult sendResult = new SendResult();
sendResult.setStatus(SendResult.Status.ACK);
sendResult.setMsg(msg);
sendResult.setSendDate(new Date());
sendCallback.onSuccess(sendResult);
}
}
}
定义发送结果类:
package com.zxb.callback;
import lombok.Data;
import lombok.Getter;
import java.util.Date;
/**
* @author zxb 2022/9/24 17:20
*/
@Data
public class SendResult {
private Status status;
private String msg;
private Date sendDate;
public enum Status {
ACK(1, "成功"),
NACK(-1, "失败");
@Getter
private Integer flag;
@Getter
private String msg;
private Status(Integer flag, String msg) {
this.flag = flag;
this.msg = msg;
}
@Override
public String toString() {
return "Status{" +
"flag=" + flag +
", msg='" + msg + '\'' +
'}';
}
}
}
测试:
package com.zxb.callback;
/**
* @author zxb 2022/9/24 17:49
*/
public class SendTest {
public static void main(String[] args) {
Producer producer = new Producer();
producer.send("Hello World", new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("结果为" + sendResult);
}
@Override
public void onFail(Exception exception) {
System.out.println(exception.getMessage());
}
});
}
}
分析
生产者生产消息并发送,我们可以理解为发送异步消息,生产者无需等待消息中心给我们返回 ACK,而是继续走下面的逻辑或者继续发送消息。这里生产者发送消息调用了消息中心的 receiveMsg 方法,并将方法和回调接口传给了消息中心,消息中心就可以通过这个回调接口来通知生产者消息发送的情况。
这里生产者不是回调接口的实现类,回调接口的实现类通过匿名内部类的方式实现,由用户指定,形参通过消息中心回调传过来,然后生产者可以根据结果做一些其他的判断等操作。