詳解Spring DeferredResult異步操作使用場景
為什么使用DeferredResult?
API接口需要在指定時間內(nèi)將異步操作的結(jié)果同步返回給前端時;
Controller處理耗時任務(wù),并且需要耗時任務(wù)的返回結(jié)果時;
當一個請求到達API接口,如果該API接口的return返回值是DeferredResult,在沒有超時或者DeferredResult對象設(shè)置setResult時,接口不會返回,但是Servlet容器線程會結(jié)束,DeferredResult另起線程來進行結(jié)果處理(即這種操作提升了服務(wù)短時間的吞吐能力),并setResult,如此以來這個請求不會占用服務(wù)連接池太久,如果超時或設(shè)置setResult,接口會立即返回。
使用DeferredResult的流程:
- 瀏覽器發(fā)起異步請求
- 請求到達服務(wù)端被掛起
- 向瀏覽器進行響應(yīng),分為兩種情況:
- 調(diào)用DeferredResult.setResult(),請求被喚醒,返回結(jié)果
- 超時,返回一個你設(shè)定的結(jié)果
- 瀏覽得到響應(yīng),再次重復1,處理此次響應(yīng)結(jié)果
給人一種異步處理業(yè)務(wù),但是卻同步返回的感覺。
場景
瀏覽器向A系統(tǒng)發(fā)起請求,該請求需要等到B系統(tǒng)(如MQ)給A推送數(shù)據(jù)時,A才會立刻向瀏覽器返回數(shù)據(jù);
如果指定時間內(nèi)B未給A推送數(shù)據(jù),則返回超時。
Demo代碼
接口代碼:
/get是調(diào)用A系統(tǒng)的接口返回數(shù)據(jù);
/result模擬B系統(tǒng)向A推送數(shù)據(jù)進行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; } /** * 設(shè)置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) { // 請求超時的回調(diào)函數(shù) 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層創(chuàng)建的對象 taskMap.putIfAbsent(requestId, deferredResult::setResult); } /** * 這里相當于異步的操作方法 * 設(shè)置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. 超時
設(shè)置超時時間為8s,會一直阻塞8s,如果超時,返回默認超時的結(jié)果
8s過去,沒有setResult,返回超時
2. 進行setResult
在阻塞期間調(diào)用setResult,我這里模擬的是B系統(tǒng)推送數(shù)據(jù)給A時拋異常失敗的情況,會立刻得到返回結(jié)果
總結(jié):
當有前端需要一個耗時操作服務(wù)時,可以使用DeferredResult異步機制編寫代碼。如果不用此功能,要么自己實現(xiàn)類似的功能。要么是前端輪訓去拉處理的結(jié)果,開發(fā)量比較大。有了DeferredResult可以節(jié)省很多我們前后端的開發(fā)量。
到此這篇關(guān)于詳解Spring DeferredResult異步操作使用場景的文章就介紹到這了,更多相關(guān)Spring DeferredResult異步內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java+opencv3.2.0實現(xiàn)hough圓檢測功能
這篇文章主要為大家詳細介紹了Java+opencv3.2.0實現(xiàn)hough圓檢測,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02詳解Spring boot上配置與使用mybatis plus
這篇文章主要介紹了詳解Spring boot上配置與使用mybatis plus,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05