jiaji's blog

理解回调机制

对回调的理解

之前回调用的比较少,一直理解为观察者模式。最近思考了一波,回调并不能和某种设计模式绑定在一起,它其实只是一种通用的写法。

举个例子:有两个类A和B,B是A的属性,A可以通过函数调用B的方法,同时把自己作为参数传递到B的方法中,B的方法在完成时可以调用A的函数,这就是回调。如果A在调用B的方法时同步等待,则是同步回调;否则即为异步回调。A的引用可以作为方法参数带入B中,也可以放到ThreadLocal中进行传递。下边是具体例子的代码:

例子

首先定义一个接口ICallBack:

1
2
3
4
5
public interface ICallBack {
void onComplete();
}

A类要实现这个接口,同时A中有B的引用:

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
public class A implements ICallBack {
B b = new B();
void doBizSync() {
b.remoteCall(this);
System.out.println("doBizSync return");
}
void doBizAsync() {
A temp = this;
new Thread(() -> {
b.remoteCall(temp);
}).start();
System.out.println("doBizAsync return");
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void onComplete() {
System.out.println("call back completed");
}
}

A有两个业务函数,分别用来测试同步和异步回调。

下边是类B的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class B {
String remoteCall(A a) {
System.out.println("this will cost 5 secodes...");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
a.onComplete();
synchronized (a) {
a.notify();
}
return "remoteCall complete";
}
}

类B中用remoteCall函数来模拟远程调用的场景。在真实场景中,B为一个注入到A的service bean。

测试类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testSync() {
A a = new A();
a.doBizSync();
}
//结果:
//this will cost 5 secodes...
//call back completed
//doBizSync return
@Test
public void testAsync() {
A a = new A();
a.doBizAsync();
}
//结果:
//doBizAsync return
//this will cost 5 secodes...
//call back completed

可见异步回调中当前线程不用一直等待远程方法返回,可以去处理其他业务了。这种写法可以节省线程池资源,提高并发。当回调处理函数只有一个时,ICallBack也可以省去了,可以用lambda表达式来代替。

Servlet 3.0开启异步

Servlet 3.0支持开启异步,这样可以实现客户端和服务端的连接还在,Servlet中的线程就不用阻塞的等待在这里了,可以释放出来去做别的事情。代码例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Controller
public class ServletAsyncTestController {
@RequestMapping(value = "/test", method = {RequestMethod.GET, RequestMethod.POST})
public void asyncAjax(HttpServletRequest request, HttpServletResponse response) throws Exception {
final PrintWriter writer = response.getWriter();
writer.println("first message....\r\n");
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
final AsyncContext asyncContext = request.startAsync(request, response);
asyncContext.setTimeout(100000); //测试设置超时时间
asyncContext.start(() -> {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
writer.println("hi.....\r\n");
}
asyncContext.complete();
});
}
}

业务线程做完耗时业务后,调用asyncContext.complete(),客户端就能收到返回了。