SpringMVC異步處理的 5 種方式示例詳解
前段時(shí)間研究了下 diamond 的原理,其中有個重要的知識點(diǎn)是長連接的實(shí)現(xiàn),用到了 servlet 的異步處理。異步處理最大的好處是可以提高并發(fā)量,不阻塞當(dāng)前線程。其實(shí) Spring MVC 也支持了異步處理,本文記錄下相關(guān)的技術(shù)點(diǎn)。
異步處理 demo
如果要啟用異步返回,需要開啟 @EnableAsync。如下的代碼中,使用 DeferredResult 進(jìn)行異步處理。
請求進(jìn)來后,首先創(chuàng)建 DeferredResult 對象,設(shè)置超時(shí)時(shí)間為 60 秒。然后指定DeferredResult 在異步完成和等待超時(shí)時(shí)的回調(diào)。同步的處理只需要創(chuàng)建異步任何,然后返回DeferredResult 即可。這樣 Spring MVC 處理完此次請求后,不會立即返回 response 給客戶端,會一直等待DeferredResult 處理完成。如果DeferredResult 沒有在 60 秒內(nèi)處理完成,就會觸發(fā)超時(shí),然后返回 response 給客戶端。
@RequestMapping(value = "/async/demo")
public DeferredResult<String> async(){
// 創(chuàng)建 DeferredResult,設(shè)置超時(shí)時(shí)間 60s
DeferredResult<String> deferredResult = new DeferredResult<>((long)60 * 1000);
String uuid = UUID.randomUUID().toString();
Runnable callback = () -> manager.remove(deferredResult, uuid);
// 設(shè)置完成和超時(shí)的回調(diào)
deferredResult.onCompletion(callback);
deferredResult.onTimeout(callback);
// 創(chuàng)建異步任務(wù)
manager.addAsyncTask(deferredResult, uuid);
// 同步返回 DeferredResult
return deferredResult;
}
對于異步任務(wù)來說,需要持有DeferredResult 對象。在異步處理結(jié)束時(shí),需要手動調(diào)用DeferredResult.setResult完成輸出。調(diào)用setResult 時(shí),數(shù)據(jù)輸出寫到客戶端,然后觸發(fā)異步完成事件執(zhí)行回調(diào)。
task.getDeferredResult().setResult(ConfigJsonUtils.toJsonString(map));
使用 DeferredResult 進(jìn)行異步處理
DeferredResult 這個類代表延遲結(jié)果。DeferredResult 可以用在異步任務(wù)中,其他線程能夠獲取DeferredResult并設(shè)置DeferredResult 的返回?cái)?shù)據(jù)。通??梢允褂镁€程池、隊(duì)列等配合DeferredResult 實(shí)現(xiàn)異步處理。
根據(jù)官方描述,Spring MVC 處理流程如下:
- 把 controller 返回的 DeferredResult 保存在內(nèi)存隊(duì)列或集合當(dāng)中;
- Spring MVC 調(diào)用 request.startAsync(),開啟異步;
- DispatcherServlet 和所有的 Filter 退出當(dāng)前請求線程;
- 業(yè)務(wù)應(yīng)用在異步線程中設(shè)置 DeferredResult 的返回值, Spring MVC 會再次發(fā)送請求;
- DispatcherServlet 再次被調(diào)用,并使用 DeferredResult 的返回值;
使用 Callable 進(jìn)行異步處理
使用 Callable 進(jìn)行異步處理與 DeferredResult 類似。不同的是,Callable 會交給系統(tǒng)指定的 TaskExecutor 執(zhí)行。
根據(jù)官方描述,Spring MVC 處理流程如下:
- controller 返回 Callable ;
- Spring MVC 調(diào)用 request.startAsync(),開啟異步,提交 Callable 到一個任務(wù)線程池 ;
- DispatcherServlet 和所有的 Filter 退出當(dāng)前請求線程;
- 業(yè)務(wù)應(yīng)用在異步線程中 返回值, Spring MVC 會再次發(fā)送請求;
- DispatcherServlet 再次被調(diào)用,并使用 Callable 的返回值;
@RequestMapping(value = "/async/demo")
public Callable<String> async(){
Callable<String> callable = () -> String.valueOf(System.currentTimeMillis());
// 同步返回
return callable;
}
使用 ListenableFuture 進(jìn)行異步處理
ListenableFuture 作為返回值,與DeferredResult 類似。也需要使用者自行處理異步線程,但不支持超時(shí)、完成回調(diào),需要自行處理。
@RequestMapping(value = "/async/demo")
public ListenableFuture<String> async(){
ListenableFutureTask<String> ListenableFuture= new ListenableFutureTask<>(() -> {
return String.valueOf(System.currentTimeMillis());
});
Executors.newSingleThreadExecutor().submit(ListenableFuture);
return ListenableFuture;
}
使用 ResponseBodyEmitter 進(jìn)行異步處理
DeferredResult 和 Callable 都只能返回一個異步值。如果需要返回多個對象,就要使用 ResponseBodyEmitter。返回的每個對象都會被 HttpMessageConverter 處理并寫回輸出流。如果希望設(shè)置更多返回?cái)?shù)據(jù),如 header、status 等,可以把 ResponseBodyEmitter 作為 ResponseEntity 的實(shí)體數(shù)據(jù)返回。
@RequestMapping("/async/responseBodyEmitter")
public ResponseBodyEmitter responseBodyEmitter(){
ResponseBodyEmitter responseBodyEmitter=new ResponseBodyEmitter();
Executors.newSingleThreadExecutor().submit(() -> {
try {
responseBodyEmitter.send("demo");
responseBodyEmitter.send("test");
responseBodyEmitter.complete();
} catch (Exception ignore) {}
});
return responseBodyEmitter;
}
使用 StreamingResponseBody 進(jìn)行異步處理
如果希望跳過返回值的自動轉(zhuǎn)換,直接把輸出流寫入OutputStream,可以使用 StreamingResponseBody。也可以作為 ResponseEntity 的實(shí)體數(shù)據(jù)返回。
@RequestMapping("/async/streamingResponseBody")
public StreamingResponseBody streamingResponseBody(){
StreamingResponseBody streamingResponseBody = outputStream -> {
Executors.newSingleThreadExecutor().submit(() -> {
try {
outputStream.write("<html>streamingResponseBody</html>".getBytes());
} catch (IOException ignore) {}
});
};
return streamingResponseBody;
}
各種處理方式的對比
|
|
數(shù)據(jù)轉(zhuǎn)換 |
回調(diào) |
線程池 |
|
|
DeferredResult |
1 次 |
有 |
完成、超時(shí) |
自行處理 |
|
Callable |
1 次 |
有 |
無 |
系統(tǒng)處理 |
|
ListenableFuture |
1 次 |
有 |
無 |
自行處理 |
|
ResponseBodyEmitter |
多次 |
有 |
無 |
自行處理 |
|
StreamingResponseBody |
多次 |
無 |
無 |
自行處理 |
到此這篇關(guān)于SpringMVC異步處理的 5 種方式的文章就介紹到這了,更多相關(guān)SpringMVC異步處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
@Transactional和@DS怎樣在事務(wù)中切換數(shù)據(jù)源
這篇文章主要介紹了@Transactional和@DS怎樣在事務(wù)中切換數(shù)據(jù)源問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07
在已經(jīng)使用mybatis的項(xiàng)目里引入mybatis-plus,結(jié)果不能共存的解決
這篇文章主要介紹了在已經(jīng)使用mybatis的項(xiàng)目里引入mybatis-plus,結(jié)果不能共存的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
Java 獲取本機(jī)的IP與MAC地址實(shí)現(xiàn)詳解
這篇文章主要介紹了Java 獲取本機(jī)的IP與MAC地址實(shí)現(xiàn)詳解的相關(guān)資料,需要的朋友可以參考下2016-09-09
Java 實(shí)戰(zhàn)項(xiàng)目錘煉之嘟嘟健身房管理系統(tǒng)的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實(shí)現(xiàn)一個健身房管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11

