日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

SpringCloud微服務中Feign如何傳遞用戶Token,并保證多線程環境也可適用?

來源: 責編: 時間:2024-03-27 09:23:21 182觀看
導讀大家好,我是飄渺。在上一篇文章中,我們解決了網關層認證后向后端服務傳遞用戶信息的問題。今天我們來解決另外一個問題:如何在 OpenFeign 中傳遞 Token,并且保證多線程情況下也能適用。這是DDD&微服務系列文章的第34篇,歡

大家好,我是飄渺。4KY28資訊網——每日最新資訊28at.com

在上一篇文章中,我們解決了網關層認證后向后端服務傳遞用戶信息的問題。今天我們來解決另外一個問題:如何在 OpenFeign 中傳遞 Token,并且保證多線程情況下也能適用。4KY28資訊網——每日最新資訊28at.com

這是DDD&微服務系列文章的第34篇,歡迎持續關注!4KY28資訊網——每日最新資訊28at.com

為了方便演示,首先定義一個接口,在接口中通過 Feign 調用其他服務:4KY28資訊網——每日最新資訊28at.com

@Operation(summary = "用戶測試接口")  @GetMapping("/api/pd/customer/info")  public String info() {            String currentUser = UserContextHolder.getInstance().getCurrentUser();            log.info("feign調用方獲取當前登錄用戶:" + currentUser);       //通過feign調用遠程服務    String info = experimentClient.info();        log.info("遠程獲取用戶:" + info);    return currentUser;  }

然后在遠程接口中通過上文定義的UserContextHolder對象獲取用戶信息:4KY28資訊網——每日最新資訊28at.com

@GetMapping("/api/pd/experiment/info")  public String userInfo() {        String currentUser = UserContextHolder.getInstance().getCurrentUser();        log.info("feign被調用方獲取userToken : {} ",currentUser);        return currentUser == null ? "" : currentUser;  }

4KY28資訊網——每日最新資訊28at.com

圖片圖片4KY28資訊網——每日最新資訊28at.com

通過調用結果可知,當使用OpenFeign調用遠程服務時,接口是無法獲取到用戶 ID 的。4KY28資訊網——每日最新資訊28at.com

常規解決辦法

在使用OpenFeign請求其他服務接口時,默認不攜帶header信息,這樣就導致無法攜帶登錄用戶信息。常規情況下,我們只需要在使用 OpenFeign 調用時先從 Header 獲取 Token 信息,放入新請求即可,在項目中可以定義一個OpenFeign的攔截器來實現此功能,代碼如下所示:4KY28資訊網——每日最新資訊28at.com

public class FeignRequestConfiguration {        @Bean      public RequestInterceptor requestInterceptor(){          return new RequestInterceptor() {              @Override              public void apply(RequestTemplate template) {                  RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();                  ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;                    // 當主線程的請求執行完畢后,Servlet容器會被銷毀當前的Servlet,因此在這里需要做判空                  if (attributes != null) {                      HttpServletRequest request = attributes.getRequest();                        // 獲取userId 并傳遞 userId                                        String userId = request.getHeader(CommonConstant.X_CLIENT_TOKEN);                      if (StringUtils.hasText(userId)) {                          template.header(CommonConstant.X_CLIENT_TOKEN, userId);                      }                  }              }          };      }  }

經過上述配置以后再次調用即可在 Feign 接口中也獲取到用戶ID,如下圖所示:4KY28資訊網——每日最新資訊28at.com

圖片圖片4KY28資訊網——每日最新資訊28at.com

異步調用

上面是單線程的情況,假如我們在當前線程中又開啟了子線程去進行 Feign 調用,那么是無法從 RequestContextHolder 獲取到 Header 的。測試代碼如下:4KY28資訊網——每日最新資訊28at.com

public String info() {            String currentUser = UserContextHolder.getInstance().getCurrentUser();            log.info("feign調用方獲取當前登錄用戶:" + currentUser);        CompletableFuture<String> infoFuture = CompletableFuture.supplyAsync(experimentClient::info,executor);    String info = "";      try{          info = infoFuture.get();      } catch (Exception e) {          e.printStackTrace();          throw new RuntimeException(e);      }        log.info("遠程獲取用戶:" + info);        return currentUser;  }

在上述代碼中,通過 CompletableFuture 開啟異步線程去調用 experimentClient ,可以發現此時無法獲取到用戶信息,效果如下所示:4KY28資訊網——每日最新資訊28at.com

圖片圖片4KY28資訊網——每日最新資訊28at.com

4KY28資訊網——每日最新資訊28at.com

出現上述問題的原因是,RequestContextHolder.getRequestAttributes() 方法里面使用的一個 ThreadLocal,默認不是線程共享的,源碼如下:4KY28資訊網——每日最新資訊28at.com

public static RequestAttributes getRequestAttributes() {      RequestAttributes attributes = requestAttributesHolder.get();      if (attributes == null) {         attributes = inheritableRequestAttributesHolder.get();      }      return attributes;  }

所以主線程調用子線程時,無法獲取到主線程請求里面的 RequestAttributes。4KY28資訊網——每日最新資訊28at.com

解決辦法

原因已經清楚了,繼續觀察 RequestContextHolder.getRequestAttributes() 方法源碼,注意到如果當前線程拿不到 RequestAttributes ,它會從 inheritableRequestAttributesHolder 里面拿,再仔細觀察發現源碼設置 RequestAttributes 到 ThreadLocal 的時候有這樣一個重載方法。4KY28資訊網——每日最新資訊28at.com

/** * 給當前線程綁定屬性 * @param inheritable 是否要將屬性暴露給子線程 */public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {      ......}

這看起來符合我們的要求,只需要在主線程調用其他線程前將 RequestAttributes 對象設置為子線程共享,就能把 Header 等信息傳遞下去。4KY28資訊網——每日最新資訊28at.com

所以,在異步調用 Feign 接口時添加如下代碼即可:4KY28資訊網——每日最新資訊28at.com

RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);CompletableFuture<String> infoFuture = CompletableFuture.supplyAsync(experimentClient::info,executor);......

再次執行發現,是可以獲取到 userId 的。4KY28資訊網——每日最新資訊28at.com

這里使用CompletableFuture異步調用時需要使用自定義線程池,而不能使用默認線程池ForkJoinPool,這是為什么呢?4KY28資訊網——每日最新資訊28at.com

最佳解決方案

雖然可以在異步調用時設置 RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true); 可以實現請求頭透傳,但是每次調用都需要加上這一句,實現上還略顯麻煩。4KY28資訊網——每日最新資訊28at.com

并且我們知道了獲取不到請求頭的原因是子線程無法獲取主線程的 header 屬性,那么我們只需要定義一個數據結構,使用 InheritableThreadLocal 在內存中保存一份 header 屬性即可。在上篇文章中通過網關進行 UserID 透傳時我們是使用 ThreadLocal 保存數據,現在只需要將其換成 InheritableThreadLocal,同時在 RequestInterceptor#apply() 方法中不再通過請求頭獲取而是直接從 InheritableThreadLocal 中獲取數據。4KY28資訊網——每日最新資訊28at.com

實現過程如下:4KY28資訊網——每日最新資訊28at.com

1、重命名并修改數據結構:4KY28資訊網——每日最新資訊28at.com

首先,將 UserContextHolder 重命名為 RequestHeaderHolder,同時使用 InheritableThreadLocal 替換 ThreadLocal,以便子線程也能獲取數據。4KY28資訊網——每日最新資訊28at.com

public class RequestHeaderHolder {    private final ThreadLocal<Map<String,String>> REQUEST_HEADER_HOLDER;    //使用InheritableThreadLocal,使得共享變量可被子線程繼承    private RequestHeaderHolder() {        this.REQUEST_HEADER_HOLDER = new InheritableThreadLocal<>() {            @Override            protected Map<String, String> initialValue() {                return new HashMap<>();            }        };    }     public String getCurrentUser(){        return this.REQUEST_HEADER_HOLDER.get().get(CommonConstant.X_CLIENT_TOKEN);   }  ......}

2、修改請求攔截器:4KY28資訊網——每日最新資訊28at.com

將請求攔截器 UserTokenInterceptor 重命名為 RequestHeaderInterceptor,并將請求頭放入 RequestHeaderHolder 中。4KY28資訊網——每日最新資訊28at.com

@Slf4jpublic class RequestHeaderInterceptor implements HandlerInterceptor {        @Override    public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {        Enumeration<String> headerNames = request.getHeaderNames();        RequestHeaderHolder requestHeaderHolder = RequestHeaderHolder.getInstance();        //重新設置請求頭        while (headerNames.hasMoreElements()){            String key = headerNames.nextElement();            requestHeaderHolder.set(key,request.getHeader(key));        }        return true;    }       ......}

3、修改 Feign 配置類:在 FeignRequestConfiguration 中不再從 RequestContextHolder 獲取數據,而是從 RequestHeaderHolder 獲取數據。4KY28資訊網——每日最新資訊28at.com

@Slf4jpublic class FeignRequestConfiguration {    @Bean    public RequestInterceptor requestInterceptor(){        return template -> {            Map<String, String> headerMap = RequestHeaderHolder.getInstance().get();            if(headerMap != null){                headerMap.forEach((key, value) -> {                                       template.header(key, value);                });            }        };    }}

通過上面的改造,不管是同步調用還是子線程異步調用都可以直接通過RequestHeaderHolder.getInstance().getCurrentUser();獲取用戶信息,并且調用方無須做任何改動。4KY28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-79599-0.htmlSpringCloud微服務中Feign如何傳遞用戶Token,并保證多線程環境也可適用?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 你的Css選擇器可視化備忘錄

下一篇: 面試官:只知道v-model是modelValue語法糖,那你可以走了

標簽:
  • 熱門焦點
  • 石頭自清潔掃拖機器人G10S評測:多年黑科技集大成之作 懶人終極福音

    科技圈經常能看到一個詞叫“縫合怪”,用來形容那些把好多功能或者外觀結合在一起的產品,通常這樣的詞是貶義詞,但如果真的是產品縫合的好、縫合的實用的話,那它就成了中性詞,今
  • .NET 程序的 GDI 句柄泄露的再反思

    一、背景1. 講故事上個月我寫過一篇 如何洞察 C# 程序的 GDI 句柄泄露 文章,當時用的是 GDIView + WinDbg 把問題搞定,前者用來定位泄露資源,后者用來定位泄露代碼,后面有朋友反
  • 虛擬鍵盤 API 的妙用

    你是否在遇到過這樣的問題:移動設備上有一個固定元素,當激活虛擬鍵盤時,該元素被隱藏在了鍵盤下方?多年來,這一直是 Web 上的默認行為,在本文中,我們將探討這個問題、為什么會發生
  • 中國家電海外掘金正當時|出海專題

    作者|吳南南編輯|胡展嘉運營|陳佳慧出品|零態LT(ID:LingTai_LT)2023年,出海市場戰況空前,中國創業者在海外紛紛摩拳擦掌,以期能夠把中國的商業模式、創業理念、戰略打法輸出海外,他們依
  • 阿里大調整

    來源:產品劉有媒體報道稱,近期淘寶天貓集團啟動了近年來最大的人力制度改革,涉及員工績效、層級體系等多個核心事項,目前已形成一個初步的&ldquo;征求意見版&rdquo;:1、取消P序列
  • 華為和江淮汽車合作開發百萬元問界MPV?雙方回應來了

    8月1日消息,郭明錤今天在社交平臺發文稱,華為正在和江淮汽車合作,開發售價在100萬元的問界MPV,預計在2024年第2季度量產,銷量目標為上市首年交付5萬輛。
  • 機構稱Q2國內智能手機銷量同比下滑4% vivo份額重回第1

    7月29日消息,根據市場調查機構Counterpoint Research公布的最新報告,2023年第2季度中國智能手機銷量同比下降4%,創新自2014年以來第2季度銷量新低。報
  • 華為Mate60標準版細節曝光:經典星環相機模組回歸

    這段時間以來,關于華為新旗艦的爆料日漸密集。據此前多方爆料,今年華為將開始恢復一年雙旗艦戰略,除上半年推出的P60系列外,往年下半年的Mate系列也將
  • 三翼鳥智能家居亮相電博會,讓用戶體驗更真實

    2021電博會在青島國際會展中心開幕中,三翼鳥直接把“家”搬到了現場,成為了展會的一大看點。這也是三翼鳥繼9月9日發布了行業首個一站式定制智慧家平臺后的
Top 主站蜘蛛池模板: 鄂托克前旗| 永仁县| 兴宁市| 平武县| 紫阳县| 高唐县| 卓资县| 乐平市| 靖江市| 贵阳市| 辉县市| 米易县| 婺源县| 靖宇县| 深水埗区| 额尔古纳市| 太原市| 漯河市| 将乐县| 尼玛县| 金塔县| 旬阳县| 龙南县| 新化县| 牙克石市| 新蔡县| 丰台区| 瓮安县| 朔州市| 固原市| 巫山县| 金川县| 西安市| 巴东县| 应用必备| 文登市| 加查县| 商河县| 冕宁县| 云南省| 长汀县|