詳解Spring DeferredResult異步操作使用場(chǎng)景
為什么使用DeferredResult?
API接口需要在指定時(shí)間內(nèi)將異步操作的結(jié)果同步返回給前端時(shí);
Controller處理耗時(shí)任務(wù),并且需要耗時(shí)任務(wù)的返回結(jié)果時(shí);
當(dāng)一個(gè)請(qǐng)求到達(dá)API接口,如果該API接口的return返回值是DeferredResult,在沒有超時(shí)或者DeferredResult對(duì)象設(shè)置setResult時(shí),接口不會(huì)返回,但是Servlet容器線程會(huì)結(jié)束,DeferredResult另起線程來進(jìn)行結(jié)果處理(即這種操作提升了服務(wù)短時(shí)間的吞吐能力),并setResult,如此以來這個(gè)請(qǐng)求不會(huì)占用服務(wù)連接池太久,如果超時(shí)或設(shè)置setResult,接口會(huì)立即返回。
使用DeferredResult的流程:
- 瀏覽器發(fā)起異步請(qǐng)求
- 請(qǐng)求到達(dá)服務(wù)端被掛起
- 向?yàn)g覽器進(jìn)行響應(yīng),分為兩種情況:
- 調(diào)用DeferredResult.setResult(),請(qǐng)求被喚醒,返回結(jié)果
- 超時(shí),返回一個(gè)你設(shè)定的結(jié)果
- 瀏覽得到響應(yīng),再次重復(fù)1,處理此次響應(yīng)結(jié)果
給人一種異步處理業(yè)務(wù),但是卻同步返回的感覺。
場(chǎng)景
瀏覽器向A系統(tǒng)發(fā)起請(qǐng)求,該請(qǐng)求需要等到B系統(tǒng)(如MQ)給A推送數(shù)據(jù)時(shí),A才會(huì)立刻向?yàn)g覽器返回?cái)?shù)據(jù);
如果指定時(shí)間內(nèi)B未給A推送數(shù)據(jù),則返回超時(shí)。
Demo代碼
接口代碼:
/get是調(diào)用A系統(tǒng)的接口返回?cái)?shù)據(jù);
/result模擬B系統(tǒng)向A推送數(shù)據(jù)進(jìn)行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;
/**
* 為了方便測(cè)試,簡(jiǎn)單模擬一個(gè)
* 多個(gè)請(qǐng)求用同一個(gè)requestId會(huì)出問題
*/
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對(duì)象的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<>();
}
/**
* 將請(qǐng)求id與setResult映射
*
* @param requestId
* @param deferredResult
*/
public void process(String requestId, DeferredResult<DeferredResultResponse> deferredResult) {
// 請(qǐng)求超時(shí)的回調(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,就相當(dāng)于是傳入的DeferredResult對(duì)象的地址
// 所以下面settingResult方法中"taskMap.get(requestId)"就是Controller層創(chuàng)建的對(duì)象
taskMap.putIfAbsent(requestId, deferredResult::setResult);
}
/**
* 這里相當(dāng)于異步的操作方法
* 設(shè)置DeferredResult對(duì)象的setResult方法
*
* @param requestId
* @param deferredResultResponse
*/
public void settingResult(String requestId, DeferredResultResponse deferredResultResponse) {
if (taskMap.containsKey(requestId)) {
Consumer<DeferredResultResponse> deferredResultResponseConsumer = taskMap.get(requestId);
// 這里相當(dāng)于DeferredResult對(duì)象的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("超時(shí)"),
FAILED("失敗"),
SUCCESS("成功");
@Getter
private String desc;
Msg(String desc) {
this.desc = desc;
}
}
}
測(cè)試
1. 超時(shí)
設(shè)置超時(shí)時(shí)間為8s,會(huì)一直阻塞8s,如果超時(shí),返回默認(rèn)超時(shí)的結(jié)果

8s過去,沒有setResult,返回超時(shí)

2. 進(jìn)行setResult
在阻塞期間調(diào)用setResult,我這里模擬的是B系統(tǒng)推送數(shù)據(jù)給A時(shí)拋異常失敗的情況,會(huì)立刻得到返回結(jié)果


總結(jié):
當(dāng)有前端需要一個(gè)耗時(shí)操作服務(wù)時(shí),可以使用DeferredResult異步機(jī)制編寫代碼。如果不用此功能,要么自己實(shí)現(xiàn)類似的功能。要么是前端輪訓(xùn)去拉處理的結(jié)果,開發(fā)量比較大。有了DeferredResult可以節(jié)省很多我們前后端的開發(fā)量。
到此這篇關(guān)于詳解Spring DeferredResult異步操作使用場(chǎng)景的文章就介紹到這了,更多相關(guān)Spring DeferredResult異步內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java的Hibernate框架中的組合映射學(xué)習(xí)教程
組合映射即是指主對(duì)象和子對(duì)象關(guān)聯(lián)且擁有相同的生命周期的映射關(guān)系,這里我們將舉一些數(shù)據(jù)操作的實(shí)例,來講解Java的Hibernate框架中的組合映射學(xué)習(xí)教程2016-07-07
SpringBoot集成ShedLock實(shí)現(xiàn)分布式定時(shí)任務(wù)流程詳解
ShedLock是一個(gè)鎖,官方解釋是他永遠(yuǎn)只是一個(gè)鎖,并非是一個(gè)分布式任務(wù)調(diào)度器。一般shedLock被使用的場(chǎng)景是,你有個(gè)任務(wù),你只希望他在單個(gè)節(jié)點(diǎn)執(zhí)行,而不希望他并行執(zhí)行,而且這個(gè)任務(wù)是支持重復(fù)執(zhí)行的2023-02-02
SpringMVC攔截器實(shí)現(xiàn)登錄認(rèn)證
這篇文章主要介紹了SpringMVC攔截器實(shí)現(xiàn)登錄認(rèn)證的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Java深入數(shù)據(jù)結(jié)構(gòu)理解掌握抽象類與接口
在類中沒有包含足夠的信息來描繪一個(gè)具體的對(duì)象,這樣的類稱為抽象類,接口是Java中最重要的概念之一,它可以被理解為一種特殊的類,不同的是接口的成員沒有執(zhí)行體,是由全局常量和公共的抽象方法所組成,本文給大家介紹Java抽象類和接口,感興趣的朋友一起看看吧2022-05-05
Java實(shí)現(xiàn)藍(lán)橋杯G將軍的示例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)藍(lán)橋杯G將軍的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
SpringBoot入口類和@SpringBootApplication講解
這篇文章主要介紹了SpringBoot入口類和@SpringBootApplication講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java使用JNDI連接數(shù)據(jù)庫的實(shí)現(xiàn)方法
本文主要介紹了Java使用JNDI連接數(shù)據(jù)庫的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
JavaEE開發(fā)基于Eclipse的環(huán)境搭建以及Maven Web App的創(chuàng)建
本文主要介紹了如何在Eclipse中創(chuàng)建的Maven Project,本文是JavaEE開發(fā)的開篇,也是基礎(chǔ)。下面內(nèi)容主要包括了JDK1.8的安裝、JavaEE版本的Eclipse的安裝、Maven的安裝、Tomcat 9.0的配置、Eclipse上的M2Eclipse插件以及STS插件的安裝。2017-03-03

