SpringMVC異步處理操作(Callable和DeferredResult)
官方文檔中說(shuō)DeferredResult和Callable都是為了異步生成返回值提供基本的支持。簡(jiǎn)單來(lái)說(shuō)就是一個(gè)請(qǐng)求進(jìn)來(lái),如果你使用了DeferredResult或者Callable,在沒(méi)有得到返回?cái)?shù)據(jù)之前,DispatcherServlet和所有Filter就會(huì)退出Servlet容器線(xiàn)程,但響應(yīng)保持打開(kāi)狀態(tài),一旦返回?cái)?shù)據(jù)有了,這個(gè)DispatcherServlet就會(huì)被再次調(diào)用并且處理,以異步產(chǎn)生的方式,向請(qǐng)求端返回值。
這么做的好處就是請(qǐng)求不會(huì)長(zhǎng)時(shí)間占用服務(wù)連接池,提高服務(wù)器的吞吐量。
Callable
@GetMapping("/callable")
public Callable<String> testCallable() throws InterruptedException {
log.info("主線(xiàn)程開(kāi)始!");
Callable<String> result = new Callable<String>() {
@Override
public String call() throws Exception {
log.info("副線(xiàn)程開(kāi)始!");
Thread.sleep(1000);
log.info("副線(xiàn)程結(jié)束!");
return "SUCCESS";
}
};
log.info("主線(xiàn)程結(jié)束!");
return result;
}
輸出的結(jié)果如下:
主線(xiàn)程開(kāi)始!
主線(xiàn)程結(jié)束!
副線(xiàn)程開(kāi)始!
副線(xiàn)程結(jié)束!
主線(xiàn)程會(huì)提前返回,可以處理其他請(qǐng)求,等待有結(jié)果之后再輸出結(jié)果

DeferredResult
一旦啟用了異步請(qǐng)求處理功能 ,控制器就可以將返回值包裝在DeferredResult,控制器可以從不同的線(xiàn)程異步產(chǎn)生返回值。優(yōu)點(diǎn)就是可以實(shí)現(xiàn)兩個(gè)完全不相干的線(xiàn)程間的通信。
我們模擬如下場(chǎng)景:

由于消息隊(duì)列和應(yīng)用2的部分太繁瑣,我們使用一個(gè)類(lèi)來(lái)代替
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class MockQueue {
private String placeOrder;
private String completeOrder;
private Logger logger = LoggerFactory.getLogger(getClass());
public String getPlaceOrder() {
return placeOrder;
}
public void setPlaceOrder(String placeOrder) throws Exception {
new Thread(() -> {
logger.info("接到下單請(qǐng)求, " + placeOrder);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
this.completeOrder = placeOrder;
logger.info("下單請(qǐng)求處理完畢," + placeOrder);
}).start();
}
public String getCompleteOrder() {
return completeOrder;
}
public void setCompleteOrder(String completeOrder) {
this.completeOrder = completeOrder;
}
}
定義一個(gè)Controller即線(xiàn)程1的部分
import org.apache.commons.lang.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
@RestController
public class AsyncController {
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
private Logger logger = LoggerFactory.getLogger(getClass());
@RequestMapping("/order")
public DeferredResult<String> order() throws Exception {
logger.info("主線(xiàn)程開(kāi)始");
String orderNumber = RandomStringUtils.randomNumeric(8);
mockQueue.setPlaceOrder(orderNumber);
DeferredResult<String> result = new DeferredResult<>();
deferredResultHolder.getMap().put(orderNumber, result);
return result;
}
}
定義一個(gè)類(lèi),用來(lái)線(xiàn)程1和線(xiàn)程2之間通信的,使用@Component默認(rèn)為單例,方便通信。
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
@Component
public class DeferredResultHolder {
private Map<String, DeferredResult<String>> map = new HashMap<String, DeferredResult<String>>();
public Map<String, DeferredResult<String>> getMap() {
return map;
}
public void setMap(Map<String, DeferredResult<String>> map) {
this.map = map;
}
}
定義一個(gè)類(lèi)來(lái)監(jiān)聽(tīng)訂單是否處理完,如果處理完了的話(huà)就設(shè)置deferredResultHolder中的DeferredResult的值,就會(huì)返回結(jié)果了。
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
new Thread(() -> {
while (true) {
if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())) {
String orderNumber = mockQueue.getCompleteOrder();
logger.info("返回訂單處理結(jié)果:"+orderNumber);
deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
mockQueue.setCompleteOrder(null);
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
補(bǔ)充:springmvc使用異步處理請(qǐng)求
同步請(qǐng)求圖示:

同步處理的圖示如上:HTTP請(qǐng)求,tomcat或其他中間件會(huì)有一個(gè)相應(yīng)的線(xiàn)程來(lái)處理這個(gè)Http請(qǐng)求,所有的業(yè)務(wù)邏輯都會(huì)在這個(gè)線(xiàn)程里去執(zhí)行,最后返回Http響應(yīng)。但是tomcat等中間件,它們可以管理的線(xiàn)程數(shù)是有限的,當(dāng)數(shù)量達(dá)到一定程度之后,再有請(qǐng)求進(jìn)入,會(huì)被阻塞掉。
簡(jiǎn)單異步圖示:

異步處理過(guò)程:當(dāng)一個(gè)http請(qǐng)求進(jìn)入后,tomcat等中間件的主線(xiàn)程調(diào)用副線(xiàn)程來(lái)執(zhí)行業(yè)務(wù)邏輯,當(dāng)副線(xiàn)程處理完成后,主線(xiàn)程再返回結(jié)果,在副線(xiàn)程處理整個(gè)業(yè)務(wù)邏輯的中,主線(xiàn)程會(huì)空閑出來(lái)去出來(lái)其他請(qǐng)求,也就是說(shuō)采用上述這種模式處理http請(qǐng)求,服務(wù)器的吞吐量會(huì)有有明顯的提升。使用異步返回,需使在web.xml將version配置為3.0版本的。

在servlet及所有的filter中配置異步支持。

簡(jiǎn)單實(shí)現(xiàn)如下:

更為復(fù)雜的業(yè)務(wù)場(chǎng)景的異步返回如下所示:

Htpp請(qǐng)求通過(guò)線(xiàn)程一處理,并將消息發(fā)送到消息隊(duì)列,應(yīng)用2處于不同的服務(wù)器,其接收到消息并將消息返回,線(xiàn)程2監(jiān)聽(tīng)到處理結(jié)果,將消息返回,線(xiàn)程一及線(xiàn)程二不知道對(duì)方的存在。這種業(yè)務(wù)情況,單開(kāi)一個(gè)線(xiàn)程是無(wú)法解決的,需要使用DeferredResult類(lèi)。
簡(jiǎn)單的實(shí)現(xiàn)代碼如下:
controller層:
@Controller
@RequestMapping("/test/")
@Slf4j
public class TestController {
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
@RequestMapping("order")
@ResponseBody
public DeferredResult<String> test() throws InterruptedException {
log.info("主線(xiàn)程開(kāi)始");
String orderNo = RandomUtils.nextInt() + "";
mockQueue.setPlaceOrder(orderNo);
DeferredResult<String> result = new DeferredResult<String>();
deferredResultHolder.getMap().put(orderNo, result);
log.info("主線(xiàn)程結(jié)束");
return result;
}
}
偽消息隊(duì)列類(lèi):
@Slf4j
@Component
public class MockQueue {
private String placeOrder;
private String compeleteOrder;
public String getPlaceOrder() {
return placeOrder;
}
public void setPlaceOrder(String placeOrder) throws InterruptedException {
new Thread(()->{ log.info("收到下單的請(qǐng)求");
this.placeOrder = placeOrder;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.compeleteOrder = placeOrder;
log.info("完成下單的請(qǐng)求");}).start();
}
public String getCompeleteOrder() {
return compeleteOrder;
}
public void setCompeleteOrder(String compeleteOrder) {
this.compeleteOrder = compeleteOrder;
}
}
偽隊(duì)列監(jiān)聽(tīng)類(lèi):
@Slf4j
@Component
public class QueueListener implements ApplicationListener{
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
new Thread(() ->{
while (true){
if(StringUtils.isNotBlank(mockQueue.getCompeleteOrder())){
String orderNum = mockQueue.getCompeleteOrder();
log.info("返回訂單處理結(jié)果" + orderNum);
deferredResultHolder.getMap().get(orderNum).setResult("success");
mockQueue.setCompeleteOrder(null);
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
容器類(lèi):
@Component
public class DeferredResultHolder {
private Map<String,DeferredResult<String>> map = new HashMap<String,DeferredResult<String>>();
public Map<String, DeferredResult<String>> getMap() {
return map;
}
public void setMap(Map<String, DeferredResult<String>> map) {
this.map = map;
}
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Java開(kāi)發(fā)HashMap?key必須實(shí)現(xiàn)hashCode?equals方法原理
這篇文章主要為大家介紹了Java開(kāi)發(fā)HashMap?key必須實(shí)現(xiàn)hashCode?equals方法原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
SpringBoot導(dǎo)入Druid運(yùn)行失敗問(wèn)題
這篇文章主要介紹了SpringBoot導(dǎo)入Druid運(yùn)行失敗,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
Flask實(shí)現(xiàn)異步非阻塞請(qǐng)求功能實(shí)例解析
這篇文章主要介紹了Flask實(shí)現(xiàn)異步非阻塞請(qǐng)求功能實(shí)例解析,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02
如何使用Collections.reverse對(duì)list集合進(jìn)行降序排序
這篇文章主要介紹了Java使用Collections.reverse對(duì)list集合進(jìn)行降序排序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
SpringBoot使用Redisson實(shí)現(xiàn)延遲執(zhí)行的完整示例
這篇文章主要介紹了SpringBoot使用Redisson實(shí)現(xiàn)延遲執(zhí)行的完整示例,文中通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-06-06
詳解Java中多進(jìn)程編程的實(shí)現(xiàn)
這篇文章主要介紹了詳解Java中多進(jìn)程編程的實(shí)現(xiàn),和多線(xiàn)程一樣,多進(jìn)程同樣是實(shí)現(xiàn)并發(fā)的一種方式,需要的朋友可以參考下2015-11-11
Java異常處理UncaughtExceptionHandler使用實(shí)例代碼詳解
當(dāng)一個(gè)線(xiàn)程由于未捕獲異常即將終止時(shí),Java虛擬機(jī)將使用thread . getuncaughtexceptionhandler()查詢(xún)線(xiàn)程的uncaughtException處理程序,并調(diào)用處理程序的uncaughtException方法,將線(xiàn)程和異常作為參數(shù)傳遞2023-03-03
Java多線(xiàn)程中的Future類(lèi)詳細(xì)解讀
這篇文章主要介紹了Java多線(xiàn)程中的Future類(lèi)詳細(xì)解讀,Future表示一個(gè)可能還沒(méi)有完成的異步任務(wù)的結(jié)果,針對(duì)這個(gè)結(jié)果可以添加Callback以便在任務(wù)執(zhí)行成功或失敗后作出相應(yīng)的操作,需要的朋友可以參考下2023-11-11

