详解Spring DeferredResult异步操作使用场景

网友投稿 695 2022-11-29

详解Spring DeferredResult异步操作使用场景

详解Spring DeferredResult异步操作使用场景

目录为什么使用DeferredResult?场景Demo代码测试1. 超时2. 进行setResult总结:

为什么使用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 get(@RequestParam(value = "timeout", required = false, defaultValuehttp:// = "10000") Long timeout) {

DeferredResult 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> taskMap;

public DeferredResultService() {

taskMap = new ConcurrentHashMap<>();

}

/**

* 将请求id与setResult映射

*

* @param requestId

* @param deferredResult

http:// */

public void process(String requestId, DeferredResult 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 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可以节省很多我们前后端的开发量。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:3.空域图像处理的洪荒之力
下一篇:2.2开源的魅力:编译opencv源代码
相关文章

 发表评论

暂时没有评论,来抢沙发吧~