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

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

由于消息隊列和應(yīng)用2的部分太繁瑣,我們使用一個類來代替
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("接到下單請求, " + placeOrder);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
this.completeOrder = placeOrder;
logger.info("下單請求處理完畢," + placeOrder);
}).start();
}
public String getCompleteOrder() {
return completeOrder;
}
public void setCompleteOrder(String completeOrder) {
this.completeOrder = completeOrder;
}
}
定義一個Controller即線程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("主線程開始");
String orderNumber = RandomStringUtils.randomNumeric(8);
mockQueue.setPlaceOrder(orderNumber);
DeferredResult<String> result = new DeferredResult<>();
deferredResultHolder.getMap().put(orderNumber, result);
return result;
}
}
定義一個類,用來線程1和線程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;
}
}
定義一個類來監(jiān)聽訂單是否處理完,如果處理完了的話就設(shè)置deferredResultHolder中的DeferredResult的值,就會返回結(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();
}
}
補充:springmvc使用異步處理請求
同步請求圖示:

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

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

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

簡單實現(xiàn)如下:

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

Htpp請求通過線程一處理,并將消息發(fā)送到消息隊列,應(yīng)用2處于不同的服務(wù)器,其接收到消息并將消息返回,線程2監(jiān)聽到處理結(jié)果,將消息返回,線程一及線程二不知道對方的存在。這種業(yè)務(wù)情況,單開一個線程是無法解決的,需要使用DeferredResult類。
簡單的實現(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("主線程開始");
String orderNo = RandomUtils.nextInt() + "";
mockQueue.setPlaceOrder(orderNo);
DeferredResult<String> result = new DeferredResult<String>();
deferredResultHolder.getMap().put(orderNo, result);
log.info("主線程結(jié)束");
return result;
}
}
偽消息隊列類:
@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("收到下單的請求");
this.placeOrder = placeOrder;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.compeleteOrder = placeOrder;
log.info("完成下單的請求");}).start();
}
public String getCompeleteOrder() {
return compeleteOrder;
}
public void setCompeleteOrder(String compeleteOrder) {
this.compeleteOrder = compeleteOrder;
}
}
偽隊列監(jiān)聽類:
@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();
}
}
容器類:
@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;
}
}
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
FreeMarker如何調(diào)用Java靜態(tài)方法及靜態(tài)變量方法
這篇文章主要介紹了FreeMarker如何調(diào)用Java靜態(tài)方法及靜態(tài)變量方法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
使用java代碼實現(xiàn)一個月內(nèi)不再提醒,通用到期的問題
這篇文章主要介紹了使用java代碼實現(xiàn)一個月內(nèi)不再提醒,通用到期的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01

