<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
程式碼如下:
@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; }
呼叫報錯:
feign.FeignException: status 405 reading FeignApi#hello(Params)
改用post請求,新增@RequestBodey註解
新增@SpringQueryMaq註解,如下:
@GetMapping("/hello") String hello(@SpringQueryMap Params params) throws Exception;
各個子系統之間通過feign呼叫,每個服務提供方需要驗證每個請求header裡的token。
public void invokeFeign() throws Exception { feignService1.method(); feignService2.method(); feignService3.method(); .... }
定義攔截每次傳送feign呼叫攔截器RequestInterceptor的子類,每次傳送feign請求前將token帶入請求頭
@Configuration public class FeignTokenInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { public void apply(RequestTemplate template) { //上下文環境保持器,拿到剛進來這個請求包含的資料,而不會因為遠端資料請求頭被清除 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest();//老的請求 if (request != null) { //同步老的請求頭中的資料,這裡是獲取cookie String cookie = request.getHeader("token"); template.header("token", cookie); } } ..... }
這樣便能實現系統間通過同步方式feign呼叫的認證問題。但是如果需要在invokeFeign方法中feignService3的方法呼叫比較耗時,並且invokeFeign業務並不關心feignService3.method()方法的執行結果,此時該怎麼辦。
修改feignService3.method()方法,將其內部實現修改為非同步,這種方案依賴服務的提供方,如果feignService3服務是其他業務部門維護,並且無法修改實現為非同步,此時只能採取方案2.
通過執行緒池呼叫feignServie3.method()
public void invokeFeign() throws Exception { feignService1.method(); feignService2.method(); executor.submit(()->{ feignService3.method(); }); .... }
懷著期待的心情開啟了嘗試,你會發現呼叫feignService3方法並沒有成功,檢視紀錄檔你將會發現是由於feign傳送request請求的header中未攜帶token導致。於是百度了下feign非同步呼叫傳參,網上大部分的解決方案,如下
public void invokeFeign() throws Exception { feignService1.method(); feignService2.method(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); executor.submit(()->{ RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true); feignService3.method(); }); } }
新增了上面的程式碼後,實測無效,此時確實有些束手無策。但是真的沒無效嗎?我仔細比對通過上述手段解決問題的部落格,他們的業務程式碼和我的程式碼不同之處。確實有不同,比如這篇。其程式碼如下
@Override public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException { OrderConfirmVo confirmVo = new OrderConfirmVo(); MemberResVo memberResVo = LoginUserInterceptor.loginUser.get(); //從主執行緒中獲得所有request資料 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> { //1、遠端查詢所有地址列表 RequestContextHolder.setRequestAttributes(requestAttributes); List<MemberAddressVo> address = memberFeignService.getAddress(memberResVo.getId()); confirmVo.setAddress(address); }, executor); //2、遠端查詢購物車所選的購物項,獲得所有購物項資料 CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> { //放入子執行緒中request資料 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()); //遠端呼叫查詢是否有庫存 R hasStock = wmsFeignService.getSkusHasStock(collect); //形成一個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遠端呼叫在呼叫之前會呼叫很多攔截器,因此遠端呼叫會丟失很多請求頭 //3、查詢使用者積分 Integer integration = memberResVo.getIntegration(); confirmVo.setIntegration(integration); //其他資料自動計算 CompletableFuture.allOf(getAddressFuture,cartFuture).get(); return confirmVo; }
我們看的出來,他的業務程式碼即使是開啟多執行緒,也是等最後執行緒裡的任務都執行完成後,業務方法才結束返回,而我的業務方法並不會等feignService3呼叫完成結束,抱著嘗試的心態,我調整了下程式碼新增了CountDownLatch,讓業務方法等待feign呼叫結束後在返回。
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(); } }
不如所料,呼叫成功了。到這裡看似是解決了問題,但是與我想象的非同步差別太大了,最終業務執行緒還是需要等待feignService3.method()呼叫業務方法才能返回,而且非同步場景如傳送簡訊、訊息推播,記錄紀錄檔可能呼叫耗時,業務方法可不想等待他們執行結束,此時該怎麼解決?
只能翻原始碼 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請求和HTTP對談範圍存取物件,"session"和"global session"作用域沒有區別。對呀會不會是因為header中的引數是request作用域的原因呢,因為請求結束,所以即使在子執行緒設定請求頭,也取不到原因。回到請求攔截器RequestInterceptor檢視獲取token地方
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); //老的請求 HttpServletRequest request = attributes.getRequest(); if (request != null) { //同步老的請求頭中的資料,這裡是獲取cookie String cookie = request.getHeader("token"); template.header("token", cookie); }
果然如此,從attributes中獲取request,然後從request中獲取token。但是沒有考慮到request請求結束,request作用域的問題,此時肯定取不到header裡的token了。
那麼該怎麼解決呢?思路不能變,肯定還是圍繞著ServletRequestAttributes展開,發現他有兩個方法getAttributes和setAttribute,而且這倆方法都支援兩個作用域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); } }
既然我們的業務方法呼叫(HttpServletRequest)不會等待feignService3.method,我們可以通過
ServletRequestAttributes.setAttributes指定作用域為session呀。
此時invokeFeign程式碼如下
public void invokeFeign() throws Exception { feignService1.method(); feignService2.method(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); //在ServeletRequestAttributes中設定token,作用域為session attributes.setAttribute("token",attributes.getRequest().getHeader("token"),1); executor.submit(()->{ RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true); feignService3.method(); }); } }
然後RequestInterceptor.apply方法也做響應調整,如下
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); //老的請求 HttpServletRequest request = attributes.getRequest(); String token = (String) attributes.getAttribute("token",1); template.header("token",token); if (request != null) { //同步老的請求頭中的資料,這裡是獲取cookie String cookie = request.getHeader("token"); template.header("token", cookie); }
問題得以圓滿解決。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45