返回Callable

Spring MVC 3.2开始引入了基于Servlet 3的异步请求处理。相比以前,控制器方法已经不一定需要返回一个值,而是可以返回一个java.util.concurrent.Callable的对象,并通过Spring MVC所管理的线程来产生返回值

@Component
public class TaskService {
    public String execute()  {
        try {
            TimeUnit.SECONDS.sleep(15);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "hello";
    }
}

@RestController  
public class AsyncCallableController {  
    private final Logger logger = LoggerFactory.getLogger(this.getClass());  
    private final TaskService taskService;  

    @Autowired  
    public AsyncCallableController(TaskService taskService) {  
        this.taskService = taskService;  
    }  

    @RequestMapping(value = "/callable", method = RequestMethod.GET, produces = "text/html")  
    public Callable<String> executeSlowTask() {  
        logger.info("Request received");  
        Callable<String> callable = taskService::execute;  
        logger.info("Servlet thread released");  

        return callable;  
    }  
}
output===>
Request received  
Servlet thread released  
Slow task executed

Callable的异步请求被处理时所依次发生的事件:

  1. 控制器先返回一个Callable对象
  2. Spring MVC开始进行异步处理,并把该Callable对象提交给另一个独立线程的执行器TaskExecutor处理
  3. DispatcherServlet和所有过滤器都退出Servlet容器线程,但此时方法的响应对象仍未返回
  4. Callable对象最终产生一个返回结果,此时Spring MVC会重新把请求分派回Servlet容器,恢复处理
  5. DispatcherServlet再次被调用,恢复对Callable异步处理所返回结果的处理

返回DeferredResult

DeferedResult处理流程

  1. Spring mvc的控制层接收用户的请求之后,如果要采用异步处理,那么就要返回DeferedResult<>泛型对象。在调用完控制层之后,立即回返回DeferedResult对象,此时驱动控制层的容器主线程,可以处理更多的请求
  2. 可以将DeferedResult对象作为真实响应数据的代理,而真实的数据是该对象的成员变量result,它可以是String类型,或者ModelAndView类型等
  3. 容器主线程,会调用DeferedResult对象的getResult方法,然后响应到客户端。在业务没有处理完毕时,result真实数据还没有形成,那么容器主线程会发生阻塞
  4. 业务处理完毕之后,要执行setResult方法,将真实的响应数据赋值到DeferedResult对象中。此时,异步线程会唤醒容器主线程。那么容器主线程会继续执行getResult方法,将真实数据响应到客户端
@RestController  
public class AsyncDeferredController {  
    private final Logger logger = LoggerFactory.getLogger(this.getClass());  
    private final TaskService taskService;  

    @Autowired  
    public AsyncDeferredController(TaskService taskService) {  
        this.taskService = taskService;  
    }  

    @RequestMapping(value = "/deferred", method = RequestMethod.GET, produces = "text/html")  
    public DeferredResult<String> executeSlowTask() {  
        logger.info("Request received");  
        DeferredResult<String> deferredResult = new DeferredResult<>();  
        CompletableFuture.supplyAsync(taskService::execute)  
            .whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));  
        logger.info("Servlet thread released");  

        return deferredResult;  
    }  
}

在Spring Mvc的控制层中,只要有一个用户请求便会实例化一个DeferedResult对象,然后返回该对象,进行响应客户端。只要DeferedResult对象不设置result响应的内容,则控制层的容器主线程在响应客户端上就会发生阻塞。因为SpringMVC只会实例化一个Controller对象,无论有多少个用户请求,在堆上只有一个Controller对象,因此可以添加一个成员变量List,将这些用户请求的DeferedResult对象存放到List中,然后启动一个定时线程扫描list,从而依次执行setResult方法,响应客户端

@Controller
public class DeferedResultController {


    private ConcurrentLinkedDeque<DeferredResult<String>> deferredResults =
            new ConcurrentLinkedDeque<DeferredResult<String>>();


    @RequestMapping("/getResult")
    @ResponseBody
    public DeferredResult<String> getDeferredResultController(){

        //设置 5秒就会超时
        final DeferredResult<String> stringDeferredResult = new DeferredResult<String>(1000);

        //将请求加入到队列中
        deferredResults.add(stringDeferredResult);

        final String message = "{username:wangbinghua}";

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.submit(new Runnable() {
            @Override
            public void run() {

                try {
                    Thread.sleep(1010);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //业务处理
                System.out.println("业务处理");
                stringDeferredResult.setResult(message);
            }
        });


        //setResult完毕之后,调用该方法
        stringDeferredResult.onCompletion(new Runnable() {
            @Override
            public void run() {
                System.out.println("异步调用完成");
                //响应完毕之后,将请求从队列中去除掉
                deferredResults.remove(stringDeferredResult);
            }
        });

        stringDeferredResult.onTimeout(new Runnable() {
            @Override
            public void run() {
                System.out.println("业务处理超时");
                stringDeferredResult.setResult("error:timeOut");
            }
        });
        return stringDeferredResult;
    }

    //开启线程定时扫描队列,响应客户端
    @Scheduled(fixedRate = 1000)
    public void scheduleResult(){
        System.out.println(new Date());
        for(int i = 0;i < deferredResults.size();i++){
            DeferredResult<String> deferredResult = deferredResults.getFirst();
            deferredResult.setResult("result:" + i);
        }
    }
}

DeferedResult 两个监听器(onCompletion & onTimeout)

  • 当DeferedResult对象调用setResult之后,响应完毕客户端,则直接调用onCompletion对应的方法。

  • 当业务处理相当耗时,则响应客户端超时,也会调用onCompletion对应的方法以及onTimeout方法。此时,响应客户端的内容为deferedResult.setErrorResult的内容,否则500错误。

  • 发生异常,调用onCompletion方法,此时,响应客户端的内容为deferedResult.setErrorResult的内容,否则500错误

WebAsyncTask对象使用实例

@RequestMapping("/async")
@ResponseBody
public WebAsyncTask<String> asyncTask(){

    // 1000 为超时设置
    WebAsyncTask<String> webAsyncTask = new WebAsyncTask<String>(1000,new Callable<String>(){

        @Override
        public String call() throws Exception {
            //业务逻辑处理
            Thread.sleep(5000);
            String message = "username:wangbinghua";
            return message;
        }
    });
    webAsyncTask.onCompletion(new Runnable() {
        @Override
        public void run() {
            System.out.println("调用完成");
        }
    });

    webAsyncTask.onTimeout(new Callable<String>() {
        @Override
        public String call() throws Exception {
            System.out.println("业务处理超时");
            return "<h1>Time Out</h1>";
        }
    });

    return webAsyncTask;
}

总结

Callable和Deferredresult做的是同样的事情——释放容器线程,在另一个线程上异步运行长时间的任务。不同的是谁管理执行任务的线程:Callable执行线程完毕即返回;Deferredresult通过设置返回对象值(deferredResult.setResult(result));)返回,可以在任何地方控制返回

results matching ""

    No results matching ""