经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
详解Spring DeferredResult异步操作使用场景
来源:jb51  时间:2021/10/25 11:26:04  对本文有异议

为什么使用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。

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.http.HttpStatus;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestParam;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import org.springframework.web.context.request.async.DeferredResult;
  8. @RestController
  9. @RequestMapping(value = "/deferred-result")
  10. public class DeferredResultController {
  11. @Autowired
  12. private DeferredResultService deferredResultService;
  13. /**
  14. * 为了方便测试,简单模拟一个
  15. * 多个请求用同一个requestId会出问题
  16. */
  17. private final String requestId = "haha";
  18. @GetMapping(value = "/get")
  19. public DeferredResult<DeferredResultResponse> get(@RequestParam(value = "timeout", required = false, defaultValue = "10000") Long timeout) {
  20. DeferredResult<DeferredResultResponse> deferredResult = new DeferredResult<>(timeout);
  21. deferredResultService.process(requestId, deferredResult);
  22. return deferredResult;
  23. }
  24. /**
  25. * 设置DeferredResult对象的result属性,模拟异步操作
  26. * @param desired
  27. * @return
  28. */
  29. @GetMapping(value = "/result")
  30. public String settingResult(@RequestParam(value = "desired", required = false, defaultValue = "成功") String desired) {
  31. DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
  32. if (DeferredResultResponse.Msg.SUCCESS.getDesc().equals(desired)){
  33. deferredResultResponse.setCode(HttpStatus.OK.value());
  34. deferredResultResponse.setMsg(desired);
  35. }else{
  36. deferredResultResponse.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
  37. deferredResultResponse.setMsg(DeferredResultResponse.Msg.FAILED.getDesc());
  38. }
  39. deferredResultService.settingResult(requestId, deferredResultResponse);
  40. return "Done";
  41. }
  42. }
  1. import org.springframework.http.HttpStatus;
  2. import org.springframework.stereotype.Service;
  3. import org.springframework.web.context.request.async.DeferredResult;
  4. import java.util.Map;
  5. import java.util.Optional;
  6. import java.util.concurrent.ConcurrentHashMap;
  7. import java.util.function.Consumer;
  8. @Service
  9. public class DeferredResultService {
  10. private Map<String, Consumer<DeferredResultResponse>> taskMap;
  11. public DeferredResultService() {
  12. taskMap = new ConcurrentHashMap<>();
  13. }
  14. /**
  15. * 将请求id与setResult映射
  16. *
  17. * @param requestId
  18. * @param deferredResult
  19. */
  20. public void process(String requestId, DeferredResult<DeferredResultResponse> deferredResult) {
  21. // 请求超时的回调函数
  22. deferredResult.onTimeout(() -> {
  23. taskMap.remove(requestId);
  24. DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
  25. deferredResultResponse.setCode(HttpStatus.REQUEST_TIMEOUT.value());
  26. deferredResultResponse.setMsg(DeferredResultResponse.Msg.TIMEOUT.getDesc());
  27. deferredResult.setResult(deferredResultResponse);
  28. });
  29. Optional.ofNullable(taskMap)
  30. .filter(t -> !t.containsKey(requestId))
  31. .orElseThrow(() -> new IllegalArgumentException(String.format("requestId=%s is existing", requestId)));
  32. // 这里的Consumer,就相当于是传入的DeferredResult对象的地址
  33. // 所以下面settingResult方法中"taskMap.get(requestId)"就是Controller层创建的对象
  34. taskMap.putIfAbsent(requestId, deferredResult::setResult);
  35. }
  36. /**
  37. * 这里相当于异步的操作方法
  38. * 设置DeferredResult对象的setResult方法
  39. *
  40. * @param requestId
  41. * @param deferredResultResponse
  42. */
  43. public void settingResult(String requestId, DeferredResultResponse deferredResultResponse) {
  44. if (taskMap.containsKey(requestId)) {
  45. Consumer<DeferredResultResponse> deferredResultResponseConsumer = taskMap.get(requestId);
  46. // 这里相当于DeferredResult对象的setResult方法
  47. deferredResultResponseConsumer.accept(deferredResultResponse);
  48. taskMap.remove(requestId);
  49. }
  50. }
  51. }
  1. import lombok.Data;
  2. import lombok.Getter;
  3. @Data
  4. public class DeferredResultResponse {
  5. private Integer code;
  6. private String msg;
  7. public enum Msg {
  8. TIMEOUT("超时"),
  9. FAILED("失败"),
  10. SUCCESS("成功");
  11. @Getter
  12. private String desc;
  13. Msg(String desc) {
  14. this.desc = desc;
  15. }
  16. }
  17. }

测试

1. 超时

设置超时时间为8s,会一直阻塞8s,如果超时,返回默认超时的结果

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

2. 进行setResult

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

总结:

当有前端需要一个耗时操作服务时,可以使用DeferredResult异步机制编写代码。如果不用此功能,要么自己实现类似的功能。要么是前端轮训去拉处理的结果,开发量比较大。有了DeferredResult可以节省很多我们前后端的开发量。

到此这篇关于详解Spring DeferredResult异步操作使用场景的文章就介绍到这了,更多相关Spring DeferredResult异步内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号