在有些场景中,需要用到异步转同步,什么意思呢,我举个例子,假如你和第三方有一个交互,第三方的处理方式是一个异步的,需要你提供通知地址(可能连第三方提供的查询的机制也是只有这种通知的形式),第三方会把相关处理后的信息通知到这个地址中,你接收到信息后处理。接下来这个功能呢需要提供其他第三方使用,而这个第三方呢觉得这对他而言是一个完整的事物,需要你在这一次调用中直接告诉他业务处理成功还是失败。这个时候就需要使用异步转同步了。以下提供几种处理方案:
下面分别针对这四种方式逐一介绍
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()获取AsyncContextasyncContext.addListener(AsyncListener)(这个是可选的)
asyncContext.setTimeout(3000L) (可选)asyncContext.start(Runnable)相比较于上面的复杂的示例,SpringMVC 可以非常简单的实现,直接返回一个Callable即可
- @RestController
- @RequestMapping(path = "call")
- public class CallableRest {
-
- @GetMapping(path = "get")
- public Callable
get() { - Callable
callable = new Callable() { - @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
exception() { - Callable
callable = new Callable() { - @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
get(long sleep, boolean error) { - Callable
callable = () -> { - System.out.println("do some thing");
- Thread.sleep(sleep);
-
- if (error) {
- System.out.println("出现异常,返回!!!");
- throw new RuntimeException("异常了!!!");
- }
-
- return "hello world";
- };
-
- // 指定3s的超时
- WebAsyncTask
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
cache = new ConcurrentHashMap<>(); -
- @GetMapping(path = "get")
- public DeferredResult
get(String id) { - DeferredResult
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
res = cache.get(id); - if (res == null) {
- return "no consumer!";
- }
-
- res.setResult(content);
- return "over!";
- }
- }
上面实例中,请求第一个接口/defer/get不会马上获得结果,直到请求了第二个接口/defer/send之后第一个接口/defer/get才返回了结果,注意:记得设置超时时间
可以通过以下两种方式设置
- @Configuration
- @EnableWebMvc
- public class WebConf implements WebMvcConfigurer {
-
- @Override
- public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
- // 超时时间设置为60s
- configurer.setDefaultTimeout(TimeUnit.SECONDS.toMillis(30));
- }
- }