最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

解決Spring Cloud feign GET請(qǐng)求無法用實(shí)體傳參的問題

 更新時(shí)間:2023年01月01日 12:38:00   作者:程序員DMZ  
這篇文章主要介紹了解決Spring Cloud feign GET請(qǐng)求無法用實(shí)體傳參的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

Spring Cloud feign GET請(qǐng)求無法用實(shí)體傳參

代碼如下:

@FeignClient(name = "eureka-client", fallbackFactory = FallBack.class, decode404 = true, path = "/client")
public interface FeignApi {
// ? ?@PostMapping("/hello/{who}")
// ? ?String hello(@PathVariable(value = "who") String who) throws Exception;

? ? @GetMapping("/hello")
? ? String hello(Params params) throws Exception;
}

調(diào)用報(bào)錯(cuò):

feign.FeignException: status 405 reading FeignApi#hello(Params)

解決辦法

改用post請(qǐng)求,添加@RequestBodey注解

新增@SpringQueryMaq注解,如下:

@GetMapping("/hello")
String hello(@SpringQueryMap Params params) throws Exception;

Spring Cloud Feign異步調(diào)用傳參問題

各個(gè)子系統(tǒng)之間通過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的子類,每次發(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)來這個(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)間通過同步方式feign調(diào)用的認(rèn)證問題。但是如果需要在invokeFeign方法中feignService3的方法調(diào)用比較耗時(shí),并且invokeFeign業(yè)務(wù)并不關(guān)心feignService3.method()方法的執(zhí)行結(jié)果,此時(shí)該怎么辦。

方案1

修改feignService3.method()方法,將其內(nèi)部實(shí)現(xiàn)修改為異步,這種方案依賴服務(wù)的提供方,如果feignService3服務(wù)是其他業(yè)務(wù)部門維護(hù),并且無法修改實(shí)現(xiàn)為異步,此時(shí)只能采取方案2.

方案2

通過線程池調(diào)用feignServie3.method()

public void invokeFeign() throws Exception {
? ? feignService1.method();
? ? feignService2.method();
? ? executor.submit(()->{
? ? ? ? feignService3.method();
? ? });
....
}

懷著期待的心情開啟了嘗試,你會(huì)發(fā)現(xiàn)調(diào)用feignService3方法并沒有成功,查看日志你將會(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è)無效,此時(shí)確實(shí)有些束手無策。但是真的沒無效嗎?我仔細(xì)比對(duì)通過上述手段解決問題的博客,他們的業(yè)務(wù)代碼和我的代碼不同之處。確實(shí)有不同,比如這篇。其代碼如下

@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
? ? OrderConfirmVo confirmVo = new OrderConfirmVo();
? ? MemberResVo memberResVo = LoginUserInterceptor.loginUser.get();
? ? //從主線程中獲得所有request數(shù)據(jù)
? ? RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
? ? CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
? ? ? ? //1、遠(yuǎn)程查詢所有地址列表
? ? ? ? RequestContextHolder.setRequestAttributes(requestAttributes);
? ? ? ? List<MemberAddressVo> address = memberFeignService.getAddress(memberResVo.getId());
? ? ? ? confirmVo.setAddress(address);
? ? }, executor);
?
? ? //2、遠(yuǎn)程查詢購(gòu)物車所選的購(gòu)物項(xiàng),獲得所有購(gòu)物項(xiàng)數(shù)據(jù)
? ? CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
? ? ? ? //放入子線程中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)用查詢是否有庫(kù)存
? ? ? ? R hasStock = wmsFeignService.getSkusHasStock(collect);
? ? ? ? //形成一個(gè)List集合,獲取所有物品是否有貨的情況
? ? ? ? List<SkuStockVo> data = hasStock.getData(new TypeReference<List<SkuStockVo>>() {
? ? ? ? });
? ? ? ? if (data!=null){
? ? ? ? ? ? //收集起來,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、查詢用戶積分
? ? Integer integration = memberResVo.getIntegration();
? ? confirmVo.setIntegration(integration);
? ? //其他數(shù)據(jù)自動(dòng)計(jì)算
?
? ? CompletableFuture.allOf(getAddressFuture,cartFuture).get();
? ? return confirmVo;
}

我們看的出來,他的業(yè)務(wù)代碼即使是開啟多線程,也是等最后線程里的任務(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)用成功了。到這里看似是解決了問題,但是與我想象的異步差別太大了,最終業(yè)務(wù)線程還是需要等待feignService3.method()調(diào)用業(yè)務(wù)方法才能返回,而且異步場(chǎng)景如發(fā)送短信、消息推送,記錄日志可能調(diào)用耗時(shí),業(yè)務(wù)方法可不想等待他們執(zhí)行結(jié)束,此時(shí)該怎么解決?

只能翻源碼 ServletRequestAttributes.java

首先看到了注釋,這給了我靈感

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ì)話范圍訪問對(duì)象,"session"和"global session"作用域沒有區(qū)別。對(duì)呀會(huì)不會(huì)是因?yàn)閔eader中的參數(shù)是request作用域的原因呢,因?yàn)檎?qǐng)求結(jié)束,所以即使在子線程設(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。但是沒有考慮到request請(qǐng)求結(jié)束,request作用域的問題,此時(shí)肯定取不到header里的token了。

那么該怎么解決呢?思路不能變,肯定還是圍繞著ServletRequestAttributes展開,發(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,我們可以通過
ServletRequestAttributes.setAttributes指定作用域?yàn)閟ession呀。

此時(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);
? ? ? ? }

問題得以圓滿解決。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論

车险| 巫山县| 沭阳县| 扎鲁特旗| 谷城县| 钟祥市| 太谷县| 横山县| 上犹县| 沅陵县| 米脂县| 汕头市| 留坝县| 和田县| 磴口县| 铁岭县| 延寿县| 富蕴县| 延长县| 南昌市| 德州市| 博白县| 蒙城县| 江山市| 桦甸市| 天门市| 赤壁市| 北安市| 仙游县| 盐池县| 威远县| 车致| 乐山市| 娱乐| 涿州市| 岚皋县| 中阳县| 梅河口市| 治多县| 双鸭山市| 巴塘县|