• DeferredResult异步处理spring mvc Demo


    一、概述

    spring mvc同步接口在请求处理过程中一直处于阻塞状态,而异步接口可以启用后台线程去处理耗时任务。简单来说适用场景:
    1.高并发;
    2.高IO耗时操作。

    二、Demo

    Spring MVC3.2之后支持异步请求,能够在controller中返回一个Callable或者DeferredResult。
    1.Callable实例

    @Controller
    public class CallableController {
        @RequestMapping(path = "/async1", method = RequestMethod.GET)
        @ResponseBody
        public Callable<String> asyncRequest() {
            return () -> {
                final long currentThread = Thread.currentThread().getId();
                final Date requestProcessingStarted = new Date();
    
                Thread.sleep(6000L);
    
                final Date requestProcessingFinished = new Date();
    
                return String.format(
                        "request: [threadId: %s, started: %s - finished: %s]"
                        , currentThread, requestProcessingStarted, requestProcessingFinished);
            };
        }
    }
    

    2.DeferredResult使用方式与Callable类似,但在返回结果上不一样,它返回的时候实际结果可能没有生成,实际的结果可能会在另外的线程里面设置到DeferredResult中去,能实现更加复杂的业务场景。

    @Controller
    public class DeferredResultController {
        private Map<Integer, DeferredResult<String>> deferredResultMap = new HashMap<>();
    
        @ResponseBody
        @GetMapping("/get")
        public DeferredResult<String> getId(@RequestParam Integer id) throws Exception {
            System.out.println("start hello");
            DeferredResult<String> deferredResult = new DeferredResult<>();
    
            //先存起来,等待触发
            deferredResultMap.put(id, deferredResult);
            System.out.println("end hello");
            return deferredResult;
        }
    
        @ResponseBody
        @GetMapping("/set")
        public void setId(@RequestParam Integer id) throws Exception {
            // 让所有hold住的请求给与响应
            if (deferredResultMap.containsKey(id)) {
                deferredResultMap.get(id).setResult("hello " + id);
            }
        }
    }
    

    当从浏览器请求http://localhost:8080/get/1时,页面处于等待状态;当访问http://localhost:8080/set/1,前面的页面会返回"hello 1"。

    处理过程:

    • controller 返回一个DeferredResult,我们把它保存到内存里或者List里面(供后续访问)
    • Spring MVC调用request.startAsync(),开启异步处理
    • 与此同时将DispatcherServlet里的拦截器、Filter等等都马上退出主线程,但是response仍然保持打开的状态
    • 应用通过另外一个线程(可能是MQ消息、定时任务等)给DeferredResult set值。然后Spring MVC会把这个请求再次派发给servlet容器
    • DispatcherServlet再次被调用,然后处理后续的标准流程

    3.模拟场景:接口接收请求,推送到队列receiveQueue,后台线程处理完成后推送到resultQueue,监听器监听resultQueue将结果赋值给DeferredResult,接口响应结果。
    首先定义类Task:

    public class Task<T> {
        private DeferredResult<String> result;
        private T message;
        private Boolean isTimeout;
    

    定义MockQueue,用于管理队列及处理数据:

    @Component
    public class MockQueue {
        /**
         * 接收队列
         */
        private BlockingQueue<Task<String>> receiveQueue = new LinkedBlockingDeque<>(5000);
        /**
         * 结果队列
         */
        private BlockingQueue<Task<String>> resultQueue = new LinkedBlockingDeque<>(5000);
    
        public MockQueue() {
            this.run();
        }
    
        /**
         * 接收task
         *
         * @param task task实体
         * @throws InterruptedException
         */
        public void put(Task<String> task) throws InterruptedException {
            receiveQueue.put(task);
        }
    
        /**
         * 获取结果
         *
         * @return
         * @throws InterruptedException
         */
        public Task<String> get() throws InterruptedException {
            return resultQueue.take();
        }
    
        private void run() {
            new Thread(() -> {
                while (true) {
                    try {
                        Task<String> task = receiveQueue.take();
                        System.out.println("receive data,start process!");
                        Thread.sleep(1000);
                        task.setMessage("success");
    
                        //任务超时,跳过
                        if (task.getIsTimeout()) {
                            continue;
                        }
    
                        resultQueue.put(task);
                        System.out.println("process done!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    

    然后实现Controller异步接口:

    @Controller
    public class DeferredResultQueueController {
        @Autowired
        MockQueue queue;
    
        @ResponseBody
        @GetMapping("/test")
        public DeferredResult<String> test(@RequestParam Integer id) throws InterruptedException {
            System.out.println("start test");
            DeferredResult<String> deferredResult = new DeferredResult<>();
            Task<String> task = new Task<>(deferredResult, "任务", false);
            deferredResult.onTimeout(() -> {
                System.out.println("任务超时 id=" + id);
                task.setMessage("任务超时");
                task.setIsTimeout(true);
            });
            queue.put(task);
            return deferredResult;
        }
    }
    

    最后定义监听器,将resultQueue的结果写入DeferredResult。

    @Component
    public class QueueResultListener implements ApplicationListener<ContextRefreshedEvent> {
        @Autowired
        MockQueue mockQueue;
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
            new Thread(() -> {
                try {
                    Task<String> task = mockQueue.get();
                    task.getResult().setResult(task.getMessage());
                    System.out.println("监听器获取到结果:task=" + task);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    

    三、参考资料

    https://www.baeldung.com/spring-deferred-result
    https://cloud.tencent.com/developer/article/1497796
    https://zhuanlan.zhihu.com/p/31223106

  • 相关阅读:
    PostMan 安装步骤详解
    使用MySQL,运行系统报错Authentication method 'caching_sha2_password' is not supported.
    jmeter安装和环境变量配置
    Svn项目迁移到Git及Visual Studio 中git使用
    SQLServer 2008以上误操作数据库恢复方法
    ABP入门系列之3——创建实体/Code First创建数据表
    ABP入门系列之2——ABP模板项目
    uni-app使用Canvas绘图
    uni-app中picker组件的一个坑
    Nginx用法详解
  • 原文地址:https://www.cnblogs.com/janes/p/14048087.html
Copyright © 2020-2023  润新知