• springmvc中异步转同步


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

    1.  AsyncContext 
    2. Callable
    3. WebAsyncTask
    4. DeferredResult

    下面分别针对这四种方式逐一介绍

    AsyncContext 

    在 Servlet3.0+之后就支持了异步请求,第一种方式比较原始,相当于直接借助 Servlet 的规范来实现,当然下面的 case 并不是直接创建一个 servlet,而是借助AsyncContext来实现

    1. @RestController
    2. @RequestMapping(path = "servlet")
    3. public class ServletRest {
    4. Map asyncMap = new HashMap();
    5. @GetMapping(path = "get/{requestId}")
    6. public void get(HttpServletRequest request,@PathVariable("requestId") String requestId) {
    7. AsyncContext asyncContext = request.startAsync();
    8. asyncContext.addListener(new AsyncListener() {
    9. @Override
    10. public void onComplete(AsyncEvent asyncEvent) throws IOException {
    11. System.out.println("操作完成:" + Thread.currentThread().getName());
    12. }
    13. @Override
    14. public void onTimeout(AsyncEvent asyncEvent) throws IOException {
    15. System.out.println("超时返回!!!");
    16. asyncContext.getResponse().setCharacterEncoding("utf-8");
    17. asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
    18. asyncContext.getResponse().getWriter().println("超时了!!!!");
    19. }
    20. @Override
    21. public void onError(AsyncEvent asyncEvent) throws IOException {
    22. System.out.println("出现了m某些异常");
    23. asyncEvent.getThrowable().printStackTrace();
    24. asyncContext.getResponse().setCharacterEncoding("utf-8");
    25. asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
    26. asyncContext.getResponse().getWriter().println("出现了某些异常哦!!!!");
    27. }
    28. @Override
    29. public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
    30. System.out.println("开始执行");
    31. }
    32. });
    33. asyncContext.setTimeout(3000L);
    34. asyncMap.put(requestId,asyncContext);
    35. System.out.println("主线程over!!! " + Thread.currentThread().getName());
    36. }
    37. @PostMapping(path = "send/{requestId}")
    38. public String send(@PathVariable("requestId") String requestId) {
    39. AsyncContext asyncContext = asyncMap.get(requestId);
    40. asyncContext.start(new Runnable() {
    41. @Override
    42. public void run() {
    43. try {
    44. System.out.println("内部线程:" + Thread.currentThread().getName());
    45. asyncContext.getResponse().setCharacterEncoding("utf-8");
    46. asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
    47. asyncContext.getResponse().getWriter().println("异步返回!");
    48. asyncContext.getResponse().getWriter().flush();
    49. // 异步完成,释放
    50. asyncContext.complete();
    51. } catch (Exception e) {
    52. e.printStackTrace();
    53. }
    54. }
    55. });
    56. return "发送完成";
    57. }
    58. }

    完整的实现如上,简单的来看一下一般步骤

    • javax.servlet.ServletRequest#startAsync()获取AsyncContext
    • 添加监听器 asyncContext.addListener(AsyncListener)(这个是可选的)
      • 用户请求开始、超时、异常、完成时回调
    • 设置超时时间 asyncContext.setTimeout(3000L) (可选)
    • 异步任务asyncContext.start(Runnable)

    Callable

    相比较于上面的复杂的示例,SpringMVC 可以非常简单的实现,直接返回一个Callable即可

    1. @RestController
    2. @RequestMapping(path = "call")
    3. public class CallableRest {
    4. @GetMapping(path = "get")
    5. public Callable get() {
    6. Callable callable = new Callable() {
    7. @Override
    8. public String call() throws Exception {
    9. System.out.println("do some thing");
    10. Thread.sleep(1000);
    11. System.out.println("执行完毕,返回!!!");
    12. return "over!";
    13. }
    14. };
    15. return callable;
    16. }
    17. @GetMapping(path = "exception")
    18. public Callable exception() {
    19. Callable callable = new Callable() {
    20. @Override
    21. public String call() throws Exception {
    22. System.out.println("do some thing");
    23. Thread.sleep(1000);
    24. System.out.println("出现异常,返回!!!");
    25. throw new RuntimeException("some error!");
    26. }
    27. };
    28. return callable;
    29. }
    30. }

    这个就不多说了,其实就和runable差不多只是这个可以抛出异常,而且有返回值

    WebAsyncTask

    其实WebAsyncTask只是包装了一下Callable

    1. @RestController
    2. @RequestMapping(path = "task")
    3. public class WebAysncTaskRest {
    4. @GetMapping(path = "get")
    5. public WebAsyncTask get(long sleep, boolean error) {
    6. Callable callable = () -> {
    7. System.out.println("do some thing");
    8. Thread.sleep(sleep);
    9. if (error) {
    10. System.out.println("出现异常,返回!!!");
    11. throw new RuntimeException("异常了!!!");
    12. }
    13. return "hello world";
    14. };
    15. // 指定3s的超时
    16. WebAsyncTask webTask = new WebAsyncTask<>(3000, callable);
    17. webTask.onCompletion(() -> System.out.println("over!!!"));
    18. webTask.onTimeout(() -> {
    19. System.out.println("超时了");
    20. return "超时返回!!!";
    21. });
    22. webTask.onError(() -> {
    23. System.out.println("出现异常了!!!");
    24. return "异常返回";
    25. });
    26. return webTask;
    27. }
    28. }

    我们现在主要要讲的就是下面这个了:

    DeferredResult

    DeferredResultWebAsyncTask最大的区别就是DeferredResult不确定什么时候会返回结果

    1. @RestController
    2. @RequestMapping(path = "defer")
    3. public class DeferredResultRest {
    4. private Map cache = new ConcurrentHashMap<>();
    5. @GetMapping(path = "get")
    6. public DeferredResult get(String id) {
    7. DeferredResult res = new DeferredResult<>();
    8. cache.put(id, res);
    9. res.onCompletion(new Runnable() {
    10. @Override
    11. public void run() {
    12. System.out.println("over!");
    13. }
    14. });
    15. return res;
    16. }
    17. @GetMapping(path = "send")
    18. public String publish(String id, String content) {
    19. DeferredResult res = cache.get(id);
    20. if (res == null) {
    21. return "no consumer!";
    22. }
    23. res.setResult(content);
    24. return "over!";
    25. }
    26. }

    上面实例中,请求第一个接口/defer/get不会马上获得结果,直到请求了第二个接口/defer/send之后第一个接口/defer/get才返回了结果,注意:记得设置超时时间

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

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

  • 相关阅读:
    C语言 3 —— 输入输出
    Apipost现已支持连接数据库!
    数据结构——栈和队列(栈的顺序存储结构和链式存储结构)
    02-csa练习题
    文件的打开方式
    shell连接Oracle 监控表数据实时性
    MPC:百万富翁问题
    Linux获取纳秒级别时间
    头门港大屏
    一次 Keepalived 高可用的事故,让我重学了一遍它!
  • 原文地址:https://blog.csdn.net/wsyyyyy/article/details/127100149