Java接口回调
回调模式在web开发中用的较多,本文简单介绍了Java的回调机制,理解此文可以在生产中写出适应业务的回调模型。
模块之间的调用 #
在一个应用系统中,必然存在模块之间的调用,调用的方式有几种:
1. 同步调用 #
同步调用
方法A()调用方法B(),并且等待方法B()的返回,然后A()再执行下一步操作。
此方法适用于B()方法执行的时间不长,如若不然,那么A()方法会长时间等待B()方法执行完成而处于阻塞状态,如此,可能会导致整个流程的阻塞。
2. 异步调用 #
异步调用
为了解决A()方法调用B()方法造成流程阻塞而出现的,最典型的方式就是开启新线程。这样的话,A()方法不必等待B()方法的返回而继续执行。
但是这种方法存在一个问题:如果A()需要知道B()的执行结果(根据业务需求,有些操作如异步线程刷新缓存、推送通知等等不需要,而有些如更改状态的操作则需要),则需要通过某种方式对B()的执行结果进行监听,在Java中可以通过 Future+Callable来实现。
3. 回调 #
回调模式
在回调模式中,
A()方法调用B()方法 B()方法执行完毕之后,调用A类的AA()方法(回调方法),将执行结果返回给A
要实现这个需求,有几点问题需要思考:
- A类中如何调用B类的方法(这个简单,字段注入即可)
- B类的方法执行完成之后,如何调用A类的callback方法(被调用的B类的方法,必须有A类对象作为形参)
- 如何提升代码的可复用性及可扩展性?(面向接口)
一个简单的例子 #
场景描述:男孩需要向女孩表白,但羞于表达,只好借助神父传达心意,
神父知道男孩的请求之后,将要对女孩说的话告诉男孩。
1/**
2 * @author:wangy
3 * @date: 2018/8/30 / 20:37
4 * @description: 这是那个男孩
5 * java 回调机制的引入 A--调用-->B(c) ---调用-->A(d) d 方法称之为回调方法
6 * 使用场景: A想完成某事, 但A不能独立完成,需要借助B的力量, B完成之后,
7 * 要将结果通知给A(通过调用A的方法实现)
8 */
9public class Kidd {
10 private String name;
11 private GodFather godFather;
12
13 public Kidd(String name,GodFather godFather) {
14 this.name = name;
15 this.godFather = godFather;
16 }
17
18 public void askGodFather(){
19 godFather.witness(this);
20 }
21 public void confess(String voice){
22 System.out.println(this.name+":"+ voice);
23 }
24}
男孩类有两个方法:
askGodFather()
方法用于调用神父类的witness()
方法confess()
方法用于让神父类回调
1/**
2 * @author:wangy
3 * @date: 2018/8/30 / 20:45
4 * @description: 这是神父类
5 */
6public class GodFather {
7
8 public void witness(Kidd kidd) {
9 kidd.confess("the moon light is gorgeous tonight");
10 }
11}
神父类比较简单,只有一个方法witness()
,它有一个Kidd
对象作为形参,这个对象用于回调男孩类的回调方法
1// 测试类
2public class Test {
3 public static void main(String[] args) {
4 new Kidd("Alex",new GodFather()).askGodFather();
5 }
6}
返回:
Alex:the moon light is gorgeous tonight
以上是一个最简单的Java 回调机制的模型,该模型还存在一些不限于以下列出的问题:
- B类的方法形参单一,上例中形参为
Kidd
对象,那么,换成Cat
对象便又要写一个“神父类”; - 同理,A类调用的B类方法单一,上例中需求的是“表白神父”*(功能A),如果换成“免灾神父”(功能B),又要写一个神父;
- 总结起来,就是通过‘类’的方式实现回调,代码的扩展性和可复用性较差
一个相对健壮的回调机制应该是这样的 #
完整的回调模式
以上的UML图解释了Java回调的实质:
A类和B类分别为接口的实现类,这样代码的扩展性一下就提升了
另一个简单的例子 #
场景描述:老师课堂点名学生回答问题,学生解答完毕之后回答老师
回调接口(A类需实现):
1/**
2 * @author wangy
3 * @desc 先定义一个回调接口,所有的A类要实现结果回调,必需实现该类并覆写回调方法,供B类调用
4 */
5public interface Callback {
6 /**
7 * 回调接口是供B类来调用的,所以它的形参中必须包含A类调用B方法时候的返回值(对象)
8 * @param a B类方法的返回值
9 * @param student 根据功能需求传递其他参数
10 */
11 void tellAnswer(Student student , int a);
12}
B类的接口:
1/**
2 * @author wangy
3 * @desc B 类, 抽象为一个接口,方便A类对不同实现类(不同功能需求)的回调
4 */
5public interface Student {
6 /**
7 * 方法接收一个callback参数,用于指示B类执行方法之后,向谁"汇报"
8 * @param callback
9 */
10 void resolveAnswer(Callback callback);
11
12 String getName();
13}
A类实体:
1/**
2 * @author:wangy
3 * @date: 2018/9/18 / 10:17
4 * @description: A类, 实现回调接口并覆写方法
5 */
6public class Teacher implements Callback {
7 /**
8 * 接口作为属性字段,便于扩展
9 */
10 private Student student;
11
12 public Teacher(Student student) {
13 this.student = student;
14 }
15
16 /**
17 * askQuestion() 方法用于调用B类的方法
18 */
19 public void askQuestion() {
20 student.resolveAnswer(this);
21 }
22
23 /**
24 * 覆写的回调方法,用于供B类调用,以便通知执行结果
25 *
26 * @param a
27 */
28 @Override
29 public void tellAnswer(Student student , int a) {
30 System.out.println("嗯,"+student.getName()+" 完成任务花了 " + a + " 秒");
31 }
32}
A类实体有2个方法
askQuestion()
用于调用B类的方法 覆写的tellAnswer()
方法,用于接收回调结果
B类实体:
1/**
2 * @author:wangy
3 * @version:1.0
4 * @date: 2018/9/18 / 10:20
5 * @description: B类的(某种特殊功能)实现类
6 */
7public class Rookie implements Student {
8 private String name;
9
10 public Rookie(String name) {
11 this.name = name;
12 }
13
14 @Override
15 public String getName() {
16 return name;
17 }
18
19 @Override
20 public void resolveAnswer(Callback callback) {
21 // 模拟方法执行过程
22 try {
23 Thread.sleep(3000);
24 } catch (InterruptedException e) {
25 e.printStackTrace();
26 }
27 // 回调,给指定callback的实现类
28 callback.tellAnswer(this, 3);
29 }
30}
B类实体有一个方法resolveAnswer(Callback callback)
,接收一个callback参数,该参数用于调用A类的回调方法
测试类:
1public class Test {
2
3 public static void main(String[] args) {
4 Teacher teacher = new Teacher(new Rookie("alan"));
5 teacher.askQuestion();
6 }
7}
返回:
嗯,alan 完成任务花了 3 秒
分析 #
上面的例子中,对A类和B类分别进行了抽象,这样做的好处就是:
- 抽象了A类之后,对于B类来说,不必要关心是“哪位老师”叫B类完成任务,只需要完成任务就好了,也就是说我B类的方法,可以复用;
- 抽象了B类之后,对于A类来说,相对更加灵活,其调用B类的方法不仅仅只限于“one-by-one”这种模式,而是一次可以对“多个学生”进行提问,只需要将A类中的字段修改为List
即可。 回调的核心就是回调方将本身即this传递给调用方,调用方接着调用回调方的方法告诉它想要知道的信息。回调是一种思想、是一种机制,至于具体如何实现,如何通过代码将回调实现得优雅、实现得可扩展性比较高,一看开发者的个人水平,二看开发者对业务的理解程度。
同步回调与异步回调 #
上述的例子是一个典型的同步回调的示例。同步回调顾名思义就是A()调用B()之后,等待B()执行完成并且调用A()的回调函数,程序再继续执行。
异步回调就是在A()调用B()的过程中,开启一个新线程,不等待B()方法执行完成并回调之后再执行后续操作。
修改上例子中的A类中的方法,将其修改为异步回调
1public class Teacher implements Callback {
2 /**
3 * 接口作为属性字段,便于扩展
4 */
5 private Student student;
6
7 public Teacher(Student student) {
8 this.student = student;
9 }
10
11 /**
12 * askQuestion() 方法用于调用B类的方法
13 */
14 public void askQuestion() {
15 // 异步回调
16 new Thread(new Runnable() {
17 @Override
18 public void run() {
19 student.resolveAnswer(Teacher.this);
20 }
21 }).start();
22 }
23
24 @Override
25 public void tellAnswer(Student student , int a) {
26 System.out.println("知道了,"+student.getName()+" 完成任务花了 " + a + " 秒");
27 }
28}
同步回调和异步回调的选择要结合具体的业务场景,比如充值服务,要将充值的结果返回给用户,调用充值服务之后必须要等待充值接口的返回;但是如果是批量的处理(如退订业务),这时候可以用异步回调,主程序完成之后(或者页面app先返回数据给用户),后台业务逻辑继续执行,最后才业务执行的结果,可以作异步处理;