当前位置 博文首页 > blackball1998的博客:请求异步处理

    blackball1998的博客:请求异步处理

    作者:[db:作者] 时间:2021-06-19 19:53

    请求异步处理

    当我们使用Spring Boot进行web开发时,默认使用Tomcat作为Servlet的容器,而Tomcat的线程池大小默认为200,如果我们的处理流程很长,就可能因为请求过多而造成线程池里的线程占满,这时候可以使用异步线程

    返回Callable接口

    在Spring MVC中,如果我们的请求处理方法返回一个Callable接口,则会开启异步线程功能,主线程接收到请求,然后把处理交给副线程处理,这样主线程就可以空闲出来去处理别的请求

    当副线程处理完成后,再获取到副线程的处理结果,然后返回给前端

    @RestController
    public class MyController {
    
        @RequestMapping("/test")
        public Callable<String> test() {
            System.out.println("主线程" + Thread.currentThread());
            return () -> {
                System.out.println("副线程" + Thread.currentThread());
                return "SUCCESS";
            };
        }
    }
    

    发送一个测试请求,可以获取到副线程中的返回值

    在这里插入图片描述

    在后台也可以看到打印的线程信息,接收请求的线程和处理的线程是两个不一样的线程

    在这里插入图片描述

    那么接受请求和处理的请求是由两个线程完成的,在返回数据的时候又是怎么能得到处理线程的数据,并且返回给相对应的请求呢?在Spring MVC的官方文档中有答案

    在这里插入图片描述

    • Spring MVC提交CallableTaskExecutor一个单独的线程中进行处理
    • 同时,DispatcherServlet和所有过滤器退出Servlet容器线程,但响应保持打开状态
    • 当副线程执行完成时,Spring MVC将请求分派回Servlet容器以完成处理。
    • 再次调用DispatcherServlet,并且返回副线程的处理结果

    如果我们添加一个拦截器,就会发现拦截器拦截了两次请求

    public class MyInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("执行前置处理方法");
            System.out.println("拦截到的请求" + request.getRequestURI());
            return true;
        }
    }
    

    在这里插入图片描述

    这是因为再次调用DispatcherServlet并恢复原来的响应时,又被拦截器拦截到了

    使用DeferredResult类

    使用DeferredResult类,也可以实现异步处理,但是效果不同,它可以让处理请求的线程一直等待,直到DeferredResult类在另外的某处地方被处理并设置了返回值,这时候才将返回值返回给前端

    我们用一个队列来保存DeferredResult对象,模拟请求的延时处理,在/test请求中产生一个DeferredResult对象,而在/handle请求中处理这个对象,这时候/test请求才会得到响应

    @RestController
    public class MyController {
    
        private static final Queue<DeferredResult<String>> queue = new LinkedList<>();
    
        @RequestMapping("/test")
        public DeferredResult<String> test() {
            DeferredResult<String> result = new DeferredResult<>();
            queue.add(result);
            return result;
        }
    
        @RequestMapping("/handle")
        public String handle() {
            DeferredResult<String> result = queue.poll();
            result.setResult("done");
            return "success";
        }
    }
    

    首先我们发送/test请求,浏览器一直等待响应

    在这里插入图片描述

    这时候我们再发送/handle请求,可以发现此前的/test请求拿到了返回值

    在这里插入图片描述

    在这里插入图片描述

    我们还可以在构造DeferredResult对象的时候,使用构造方法设置处理的超时时间和超时时返回的结果

    @RestController
    public class MyController {
      
        @RequestMapping("/test")
        public DeferredResult<String> test() {
            DeferredResult<String> result = new DeferredResult<>(3000L, "fail");
            queue.add(result);
            return result;
        }
    }
    

    异步拦截器

    使用异步拦截器,可以在请求方法异步执行时执行拦截方法

    使用方法只需要创建自定义拦截器并实现AsyncHandlerInterceptor接口,重写afterConcurrentHandlingStarted方法,方法中的逻辑就会在请求方法异步执行时执行

    最后需要在Spring MVC的web配置中注册拦截器,方法与普通的拦截器一样

    public class MyInterceptor implements AsyncHandlerInterceptor {
    
        @Override
        public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("执行异步拦截器");
        }
    }