在有些场景中,需要用到异步转同步,什么意思呢,我举个例子,假如你和第三方有一个交互,第三方的处理方式是一个异步的,需要你提供通知地址(可能连第三方提供的查询的机制也是只有这种通知的形式),第三方会把相关处理后的信息通知到这个地址中,你接收到信息后处理。接下来这个功能呢需要提供其他第三方使用,而这个第三方呢觉得这对他而言是一个完整的事物,需要你在这一次调用中直接告诉他业务处理成功还是失败。这个时候就需要使用异步转同步了。以下提供几种处理方案:
- AsyncContext
- Callable
- WebAsyncTask
- DeferredResult
下面分别针对这四种方式逐一介绍
AsyncContext
在 Servlet3.0+之后就支持了异步请求,第一种方式比较原始,相当于直接借助 Servlet 的规范来实现,当然下面的 case 并不是直接创建一个 servlet,而是借助AsyncContext
来实现
@RestController
@RequestMapping(path = "servlet")
public class ServletRest {
Map asyncMap = new HashMap();
@GetMapping(path = "get/{requestId}")
public void get(HttpServletRequest request,@PathVariable("requestId") String requestId) {
AsyncContext asyncContext = request.startAsync();
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
System.out.println("操作完成:" + Thread.currentThread().getName());
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
System.out.println("超时返回!!!");
asyncContext.getResponse().setCharacterEncoding("utf-8");
asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
asyncContext.getResponse().getWriter().println("超时了!!!!");
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
System.out.println("出现了m某些异常");
asyncEvent.getThrowable().printStackTrace();
asyncContext.getResponse().setCharacterEncoding("utf-8");
asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
asyncContext.getResponse().getWriter().println("出现了某些异常哦!!!!");
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
System.out.println("开始执行");
}
});
asyncContext.setTimeout(3000L);
asyncMap.put(requestId,asyncContext);
System.out.println("主线程over!!! " + Thread.currentThread().getName());
}
@PostMapping(path = "send/{requestId}")
public String send(@PathVariable("requestId") String requestId) {
AsyncContext asyncContext = asyncMap.get(requestId);
asyncContext.start(new Runnable() {
@Override
public void run() {
try {
System.out.println("内部线程:" + Thread.currentThread().getName());
asyncContext.getResponse().setCharacterEncoding("utf-8");
asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
asyncContext.getResponse().getWriter().println("异步返回!");
asyncContext.getResponse().getWriter().flush();
// 异步完成,释放
asyncContext.complete();
} catch (Exception e) {
e.printStackTrace();
}
}
});
return "发送完成";
}
}
完整的实现如上,简单的来看一下一般步骤
javax.servlet.ServletRequest#startAsync()
获取AsyncContext
- 添加监听器
asyncContext.addListener(AsyncListener)
(这个是可选的)- 用户请求开始、超时、异常、完成时回调
- 设置超时时间
asyncContext.setTimeout(3000L)
(可选) - 异步任务
asyncContext.start(Runnable)
Callable
相比较于上面的复杂的示例,SpringMVC 可以非常简单的实现,直接返回一个Callable
即可
@RestController
@RequestMapping(path = "call")
public class CallableRest {
@GetMapping(path = "get")
public Callable<String> get() {
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("do some thing");
Thread.sleep(1000);
System.out.println("执行完毕,返回!!!");
return "over!";
}
};
return callable;
}
@GetMapping(path = "exception")
public Callable<String> exception() {
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("do some thing");
Thread.sleep(1000);
System.out.println("出现异常,返回!!!");
throw new RuntimeException("some error!");
}
};
return callable;
}
}
这个就不多说了,其实就和runable差不多只是这个可以抛出异常,而且有返回值
WebAsyncTask
其实WebAsyncTask只是包装了一下Callable
@RestController
@RequestMapping(path = "task")
public class WebAysncTaskRest {
@GetMapping(path = "get")
public WebAsyncTask<String> get(long sleep, boolean error) {
Callable<String> callable = () -> {
System.out.println("do some thing");
Thread.sleep(sleep);
if (error) {
System.out.println("出现异常,返回!!!");
throw new RuntimeException("异常了!!!");
}
return "hello world";
};
// 指定3s的超时
WebAsyncTask<String> webTask = new WebAsyncTask<>(3000, callable);
webTask.onCompletion(() -> System.out.println("over!!!"));
webTask.onTimeout(() -> {
System.out.println("超时了");
return "超时返回!!!";
});
webTask.onError(() -> {
System.out.println("出现异常了!!!");
return "异常返回";
});
return webTask;
}
}
我们现在主要要讲的就是下面这个了:
DeferredResult
DeferredResult
与WebAsyncTask
最大的区别就是DeferredResult
不确定什么时候会返回结果
@RestController
@RequestMapping(path = "defer")
public class DeferredResultRest {
private Map<String, DeferredResult> cache = new ConcurrentHashMap<>();
@GetMapping(path = "get")
public DeferredResult<String> get(String id) {
DeferredResult<String> res = new DeferredResult<>();
cache.put(id, res);
res.onCompletion(new Runnable() {
@Override
public void run() {
System.out.println("over!");
}
});
return res;
}
@GetMapping(path = "send")
public String publish(String id, String content) {
DeferredResult<String> res = cache.get(id);
if (res == null) {
return "no consumer!";
}
res.setResult(content);
return "over!";
}
}
上面实例中,请求第一个接口/defer/get不会马上获得结果,直到请求了第二个接口/defer/send之后第一个接口/defer/get才返回了结果,注意:记得设置超时时间
可以通过以下两种方式设置
- 构造new DeferredResult<>(30000L)时
- 设置全局超时时间,则是通过代码
@Configuration @EnableWebMvc public class WebConf implements WebMvcConfigurer { @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { // 超时时间设置为60s configurer.setDefaultTimeout(TimeUnit.SECONDS.toMillis(30)); } }