springmvc中异步转同步

在有些场景中,需要用到异步转同步,什么意思呢,我举个例子,假如你和第三方有一个交互,第三方的处理方式是一个异步的,需要你提供通知地址(可能连第三方提供的查询的机制也是只有这种通知的形式),第三方会把相关处理后的信息通知到这个地址中,你接收到信息后处理。接下来这个功能呢需要提供其他第三方使用,而这个第三方呢觉得这对他而言是一个完整的事物,需要你在这一次调用中直接告诉他业务处理成功还是失败。这个时候就需要使用异步转同步了。以下提供几种处理方案:

  1.  AsyncContext 
  2. Callable
  3. WebAsyncTask
  4. 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

DeferredResultWebAsyncTask最大的区别就是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才返回了结果,注意:记得设置超时时间

可以通过以下两种方式设置

  1. 构造new DeferredResult<>(30000L)时
  2. 设置全局超时时间,则是通过代码
    @Configuration
    @EnableWebMvc
    public class WebConf implements WebMvcConfigurer {
     
        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
            // 超时时间设置为60s
            configurer.setDefaultTimeout(TimeUnit.SECONDS.toMillis(30));
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懒人烂命

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值