解決SpringCloud?Feign異步調(diào)用傳參問(wèn)題
背景
各個(gè)子系統(tǒng)之間通過(guò)feign調(diào)用,每個(gè)服務(wù)提供方需要驗(yàn)證每個(gè)請(qǐng)求header里的token。
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
feignService3.method();
....
}定義攔截每次發(fā)送feign調(diào)用攔截器RequestInterceptor的子類(lèi),每次發(fā)送feign請(qǐng)求前將token帶入請(qǐng)求頭
@Configuration
public class FeignTokenInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
public void apply(RequestTemplate template) {
//上下文環(huán)境保持器,拿到剛進(jìn)來(lái)這個(gè)請(qǐng)求包含的數(shù)據(jù),而不會(huì)因?yàn)檫h(yuǎn)程數(shù)據(jù)請(qǐng)求頭被清除
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();//老的請(qǐng)求
if (request != null) {
//同步老的請(qǐng)求頭中的數(shù)據(jù),這里是獲取cookie
String cookie = request.getHeader("token");
template.header("token", cookie);
}
}
.....
}這樣便能實(shí)現(xiàn)系統(tǒng)間通過(guò)同步方式feign調(diào)用的認(rèn)證問(wèn)題。但是如果需要在invokeFeign方法中feignService3的方法調(diào)用比較耗時(shí),并且invokeFeign業(yè)務(wù)并不關(guān)心feignService3.method()方法的執(zhí)行結(jié)果,此時(shí)該怎么辦。
方案1:
修改feignService3.method()方法,將其內(nèi)部實(shí)現(xiàn)修改為異步,這種方案依賴(lài)服務(wù)的提供方,如果feignService3服務(wù)是其他業(yè)務(wù)部門(mén)維護(hù),并且無(wú)法修改實(shí)現(xiàn)為異步,此時(shí)只能采取方案2.
方案2:
通過(guò)線(xiàn)程池調(diào)用feignServie3.method()
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
executor.submit(()->{
feignService3.method();
});
....
}懷著期待的心情開(kāi)啟了嘗試,你會(huì)發(fā)現(xiàn)調(diào)用feignService3方法并沒(méi)有成功,查看日志你將會(huì)發(fā)現(xiàn)是由于feign發(fā)送request請(qǐng)求的header中未攜帶token導(dǎo)致。于是百度了下feign異步調(diào)用傳參,網(wǎng)上大部分的解決方案,如下
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
executor.submit(()->{
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
feignService3.method();
});
}
}添加了上面的代碼后,實(shí)測(cè)無(wú)效,此時(shí)確實(shí)有些束手無(wú)策。但是真的沒(méi)無(wú)效嗎?我仔細(xì)比對(duì)通過(guò)上述手段解決問(wèn)題的博客,他們的業(yè)務(wù)代碼和我的代碼不同之處。確實(shí)有不同,比如http://www.dbjr.com.cn/article/249407.htm這篇。其代碼如下
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
OrderConfirmVo confirmVo = new OrderConfirmVo();
MemberResVo memberResVo = LoginUserInterceptor.loginUser.get();
//從主線(xiàn)程中獲得所有request數(shù)據(jù)
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
//1、遠(yuǎn)程查詢(xún)所有地址列表
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVo> address = memberFeignService.getAddress(memberResVo.getId());
confirmVo.setAddress(address);
}, executor);
//2、遠(yuǎn)程查詢(xún)購(gòu)物車(chē)所選的購(gòu)物項(xiàng),獲得所有購(gòu)物項(xiàng)數(shù)據(jù)
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
//放入子線(xiàn)程中request數(shù)據(jù)
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> items = cartFeginService.getCurrentUserCartItems();
confirmVo.setItem(items);
}, executor).thenRunAsync(()->{
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> items = confirmVo.getItem();
List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
//遠(yuǎn)程調(diào)用查詢(xún)是否有庫(kù)存
R hasStock = wmsFeignService.getSkusHasStock(collect);
//形成一個(gè)List集合,獲取所有物品是否有貨的情況
List<SkuStockVo> data = hasStock.getData(new TypeReference<List<SkuStockVo>>() {
});
if (data!=null){
//收集起來(lái),Map<Long,Boolean> stocks;
Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
confirmVo.setStocks(map);
}
},executor);
//feign遠(yuǎn)程調(diào)用在調(diào)用之前會(huì)調(diào)用很多攔截器,因此遠(yuǎn)程調(diào)用會(huì)丟失很多請(qǐng)求頭
//3、查詢(xún)用戶(hù)積分
Integer integration = memberResVo.getIntegration();
confirmVo.setIntegration(integration);
//其他數(shù)據(jù)自動(dòng)計(jì)算
CompletableFuture.allOf(getAddressFuture,cartFuture).get();
return confirmVo;
}我們看的出來(lái),他的業(yè)務(wù)代碼即使是開(kāi)啟多線(xiàn)程,也是等最后線(xiàn)程里的任務(wù)都執(zhí)行完成后,業(yè)務(wù)方法才結(jié)束返回,而我的業(yè)務(wù)方法并不會(huì)等feignService3調(diào)用完成結(jié)束,抱著嘗試的心態(tài),我調(diào)整了下代碼添加了CountDownLatch,讓業(yè)務(wù)方法等待feign調(diào)用結(jié)束后在返回。
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
CountDownLatch latch = new CountDownLatch(1);
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
executor.submit(()->{
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
feignService3.method();
latch.countDown();
});
latch.await();
}
}不如所料,調(diào)用成功了。到這里看似是解決了問(wèn)題,但是與我想象的異步差別太大了,最終業(yè)務(wù)線(xiàn)程還是需要等待feignService3.method()調(diào)用業(yè)務(wù)方法才能返回,而且異步場(chǎng)景如發(fā)送短信、消息推送,記錄日志可能調(diào)用耗時(shí),業(yè)務(wù)方法可不想等待他們執(zhí)行結(jié)束,此時(shí)該怎么解決?只能翻源碼ServletRequestAttributes.java
首先看到了注釋?zhuān)@給了我靈感
Servlet-based implementation of the {@link RequestAttributes} interface. <p>Accesses objects from servlet request and HTTP session scope,
with no distinction between "session" and "global session".從servlet請(qǐng)求和HTTP會(huì)話(huà)范圍訪(fǎng)問(wèn)對(duì)象,"session"和"global session"作用域沒(méi)有區(qū)別。對(duì)呀會(huì)不會(huì)是因?yàn)?code>header中的參數(shù)是request作用域的原因呢,因?yàn)檎?qǐng)求結(jié)束,所以即使在子線(xiàn)程設(shè)置請(qǐng)求頭,也取不到原因?;氐秸?qǐng)求攔截器RequestInterceptor查看獲取token地方
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//老的請(qǐng)求
HttpServletRequest request = attributes.getRequest();
if (request != null) {
//同步老的請(qǐng)求頭中的數(shù)據(jù),這里是獲取cookie
String cookie = request.getHeader("token");
template.header("token", cookie);
}果然如此,從attributes中獲取request,然后從request中獲取token。但是沒(méi)有考慮到request請(qǐng)求結(jié)束,request作用域的問(wèn)題,此時(shí)肯定取不到header里的token了。
那么該怎么解決呢?思路不能變,肯定還是圍繞著ServletRequestAttributes展開(kāi),發(fā)現(xiàn)他有兩個(gè)方法getAttributes和setAttribute,而且這倆方法都支持兩個(gè)作用域request、session。
@Override
public Object getAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attribute - request is not active anymore!");
}
return this.request.getAttribute(name);
}
else {
HttpSession session = getSession(false);
if (session != null) {
try {
Object value = session.getAttribute(name);
if (value != null) {
this.sessionAttributesToUpdate.put(name, value);
}
return value;
}
catch (IllegalStateException ex) {
// Session invalidated - shouldn't usually happen.
}
}
return null;
}
}
@Override
public void setAttribute(String name, Object value, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
}
else {
HttpSession session = obtainSession();
this.sessionAttributesToUpdate.remove(name);
session.setAttribute(name, value);
}
}既然我們的業(yè)務(wù)方法調(diào)用(HttpServletRequest)不會(huì)等待feignService3.method,我們可以通過(guò)ServletRequestAttributes.setAttributes指定作用域?yàn)?code>session呀。此時(shí)invokeFeign代碼如下
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
//在ServeletRequestAttributes中設(shè)置token,作用域?yàn)閟ession
attributes.setAttribute("token",attributes.getRequest().getHeader("token"),1);
executor.submit(()->{
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
feignService3.method();
});
}
}然后RequestInterceptor.apply方法也做響應(yīng)調(diào)整,如下
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//老的請(qǐng)求
HttpServletRequest request = attributes.getRequest();
String token = (String) attributes.getAttribute("token",1);
template.header("token",token);
if (request != null) {
//同步老的請(qǐng)求頭中的數(shù)據(jù),這里是獲取cookie
String cookie = request.getHeader("token");
template.header("token", cookie);
}問(wèn)題得以圓滿(mǎn)解決。
到此這篇關(guān)于SpringCloud Feign異步調(diào)用傳參問(wèn)題的文章就介紹到這了,更多相關(guān)SpringCloud Feign傳參內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java文件讀取寫(xiě)入后 md5值不變的實(shí)現(xiàn)方法
下面小編就為大家分享一篇Java文件讀取寫(xiě)入后 md5值不變的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-11-11
關(guān)于Spring?Cloud實(shí)現(xiàn)日志管理模塊
這篇文章主要介紹了關(guān)于Spring?Cloud實(shí)現(xiàn)日志管理模塊問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
Java面向?qū)ο笾b類(lèi)的用途與實(shí)際使用
所謂包裝類(lèi),就是能夠直接將簡(jiǎn)單類(lèi)型的變量表示為一個(gè)類(lèi),在執(zhí)行變量類(lèi)型的相互轉(zhuǎn)換時(shí),我們會(huì)大量使用這些包裝類(lèi),本文我們來(lái)深入探索一下Java包裝類(lèi)的相關(guān)內(nèi)容,需要的朋友可以參考下2022-03-03
Java Comparator.comparing比較導(dǎo)致空指針異常的解決
這篇文章主要介紹了Java Comparator.comparing比較導(dǎo)致空指針異常的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Spring Batch讀取txt文件并寫(xiě)入數(shù)據(jù)庫(kù)的方法教程
這篇文章主要給大家介紹了Spring Batch讀取txt文件并寫(xiě)入數(shù)據(jù)庫(kù)的方法,SpringBatch 是一個(gè)輕量級(jí)、全面的批處理框架。這里我們用它來(lái)實(shí)現(xiàn)文件的讀取并將讀取的結(jié)果作處理,處理之后再寫(xiě)入數(shù)據(jù)庫(kù)中的功能。需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-04-04
mybatis-plus之如何根據(jù)數(shù)據(jù)庫(kù)主鍵定義字段類(lèi)型

