为什么使用DeferredResult?
API接口需要在指定时间内将异步操作的结果同步返回给前端时;
Controller处理耗时任务,并且需要耗时任务的返回结果时;
当一个请求到达API接口,如果该API接口的return返回值是DeferredResult,在没有超时或者DeferredResult对象设置setResult时,接口不会返回,但是Servlet容器线程会结束,DeferredResult另起线程来进行结果处理(即这种操作提升了服务短时间的吞吐能力),并setResult,如此以来这个请求不会占用服务连接池太久,如果超时或设置setResult,接口会立即返回。
使用DeferredResult的流程:
- 浏览器发起异步请求
- 请求到达服务端被挂起
- 向浏览器进行响应,分为两种情况:
- 调用DeferredResult.setResult(),请求被唤醒,返回结果
- 超时,返回一个你设定的结果
- 浏览得到响应,再次重复1,处理此次响应结果
给人一种异步处理业务,但是却同步返回的感觉。
场景
浏览器向A系统发起请求,该请求需要等到B系统(如MQ)给A推送数据时,A才会立刻向浏览器返回数据;
如果指定时间内B未给A推送数据,则返回超时。
Demo代码
接口代码:
/get是调用A系统的接口返回数据;
/result模拟B系统向A推送数据进行setResult。
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.http.HttpStatus;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.context.request.async.DeferredResult;
-
-
- @RestController
- @RequestMapping(value = "/deferred-result")
- public class DeferredResultController {
-
- @Autowired
- private DeferredResultService deferredResultService;
-
- /**
- * 为了方便测试,简单模拟一个
- * 多个请求用同一个requestId会出问题
- */
- private final String requestId = "haha";
-
-
- @GetMapping(value = "/get")
- public DeferredResult<DeferredResultResponse> get(@RequestParam(value = "timeout", required = false, defaultValue = "10000") Long timeout) {
- DeferredResult<DeferredResultResponse> deferredResult = new DeferredResult<>(timeout);
-
- deferredResultService.process(requestId, deferredResult);
-
- return deferredResult;
- }
-
- /**
- * 设置DeferredResult对象的result属性,模拟异步操作
- * @param desired
- * @return
- */
- @GetMapping(value = "/result")
- public String settingResult(@RequestParam(value = "desired", required = false, defaultValue = "成功") String desired) {
- DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
- if (DeferredResultResponse.Msg.SUCCESS.getDesc().equals(desired)){
- deferredResultResponse.setCode(HttpStatus.OK.value());
- deferredResultResponse.setMsg(desired);
- }else{
- deferredResultResponse.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
- deferredResultResponse.setMsg(DeferredResultResponse.Msg.FAILED.getDesc());
- }
- deferredResultService.settingResult(requestId, deferredResultResponse);
-
- return "Done";
- }
- }
- import org.springframework.http.HttpStatus;
- import org.springframework.stereotype.Service;
- import org.springframework.web.context.request.async.DeferredResult;
-
- import java.util.Map;
- import java.util.Optional;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.function.Consumer;
-
- @Service
- public class DeferredResultService {
-
- private Map<String, Consumer<DeferredResultResponse>> taskMap;
-
- public DeferredResultService() {
- taskMap = new ConcurrentHashMap<>();
- }
-
- /**
- * 将请求id与setResult映射
- *
- * @param requestId
- * @param deferredResult
- */
- public void process(String requestId, DeferredResult<DeferredResultResponse> deferredResult) {
- // 请求超时的回调函数
- deferredResult.onTimeout(() -> {
- taskMap.remove(requestId);
- DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
- deferredResultResponse.setCode(HttpStatus.REQUEST_TIMEOUT.value());
- deferredResultResponse.setMsg(DeferredResultResponse.Msg.TIMEOUT.getDesc());
- deferredResult.setResult(deferredResultResponse);
- });
-
- Optional.ofNullable(taskMap)
- .filter(t -> !t.containsKey(requestId))
- .orElseThrow(() -> new IllegalArgumentException(String.format("requestId=%s is existing", requestId)));
-
- // 这里的Consumer,就相当于是传入的DeferredResult对象的地址
- // 所以下面settingResult方法中"taskMap.get(requestId)"就是Controller层创建的对象
- taskMap.putIfAbsent(requestId, deferredResult::setResult);
- }
-
- /**
- * 这里相当于异步的操作方法
- * 设置DeferredResult对象的setResult方法
- *
- * @param requestId
- * @param deferredResultResponse
- */
- public void settingResult(String requestId, DeferredResultResponse deferredResultResponse) {
- if (taskMap.containsKey(requestId)) {
- Consumer<DeferredResultResponse> deferredResultResponseConsumer = taskMap.get(requestId);
- // 这里相当于DeferredResult对象的setResult方法
- deferredResultResponseConsumer.accept(deferredResultResponse);
- taskMap.remove(requestId);
- }
- }
-
- }
- import lombok.Data;
- import lombok.Getter;
-
- @Data
- public class DeferredResultResponse {
- private Integer code;
- private String msg;
-
- public enum Msg {
- TIMEOUT("超时"),
- FAILED("失败"),
- SUCCESS("成功");
-
- @Getter
- private String desc;
-
- Msg(String desc) {
- this.desc = desc;
- }
- }
- }
测试
1. 超时
设置超时时间为8s,会一直阻塞8s,如果超时,返回默认超时的结果

8s过去,没有setResult,返回超时

2. 进行setResult
在阻塞期间调用setResult,我这里模拟的是B系统推送数据给A时抛异常失败的情况,会立刻得到返回结果


总结:
当有前端需要一个耗时操作服务时,可以使用DeferredResult异步机制编写代码。如果不用此功能,要么自己实现类似的功能。要么是前端轮训去拉处理的结果,开发量比较大。有了DeferredResult可以节省很多我们前后端的开发量。
到此这篇关于详解Spring DeferredResult异步操作使用场景的文章就介绍到这了,更多相关Spring DeferredResult异步内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!