91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringCloud?OpenFeign服務調用傳遞token的方法

發布時間:2022-07-26 16:57:56 來源:億速云 閱讀:366 作者:iii 欄目:開發技術

這篇文章主要介紹“SpringCloud OpenFeign服務調用傳遞token的方法”,在日常操作中,相信很多人在SpringCloud OpenFeign服務調用傳遞token的方法問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”SpringCloud OpenFeign服務調用傳遞token的方法”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    業務場景

    通常微服務對于用戶認證信息解析有兩種方案

    • gateway 就解析用戶的 token 然后路由的時候把 userId 等相關信息添加到 header 中傳遞下去。

    • gateway 直接把 token 傳遞下去,每個子微服務自己在過濾器解析 token

    現在有一個從 A 服務調用 B 服務接口的內部調用業務場景,無論是哪種方案我們都需要把 header 從 A 服務傳遞到 B 服務。

    RequestInterceptor

    OpenFeign 給我們提供了一個請求攔截器 RequestInterceptor ,我們可以實現這個接口重寫 apply 方法將當前請求的 header 添加到請求中去,傳遞給下游服務,RequestContextHolder 可以獲得當前線程綁定的 Request 對象

    /** Feign 調用的時候傳token到下游 */
    public class FeignRequestInterceptor implements RequestInterceptor {
      @Override
      public void apply(RequestTemplate template) {
        // 從header獲取X-token
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes attr = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = attr.getRequest();
        String token = request.getHeader("x-auth-token");//網關傳過來的 token
        if (StringUtils.hasText(token)) {
          template.header("X-AUTH-TOKEN", token);
        }
      }
    }

    然后在 @FeignClient 中使用

    @FeignClient(
        ...
        configuration = {FeignClientDecoderConfiguration.class, FeignRequestInterceptor.class})
    public interface AuthCenterClient {

    多線程環境下傳遞 header(一)

    上面是單線程的情況,假如我們在當前線程中又開啟了子線程去進行 Feign 調用,那么是無法從 RequestContextHolder 獲取到 header 的,原因很簡單,看下 RequestContextHolder 源碼就知道了,它里面是一個 ThreadLocal ,線程都變了,那肯定獲取不到主線程請求里面的 requestAttribute 了。

    原因已經清楚了,現在想辦法去解決它。觀察 RequestContextHolder.getRequestAttributes() 方法源碼

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

    注意到如果當前線程拿不到 RequestAttributes ,他會從 inheritableRequestAttributesHolder 里面拿,再仔細觀察發現源碼設置 RequestAttributesThreadLocal 的時候有這樣一個重載方法

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

    這特喵的完美符合我們的需求,現在我們的問題就是子線程沒有拿到主線程的 RequestContextHolder 里面的屬性。在業務代碼中:

    RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
    log.info("主線程任務....");
    new Thread(() -> {
        log.info("子線程任務開始...");
        UserResponse response = client.getById(3L);
    }).start();

    開發環境測試之后發現子線程已經能夠從 RequestContextHolder 拿到主線程的請求對象了。

    分析 inheritableRequestAttributesHolder 原理

    觀察源碼我們可以看到這個屬性的類型是 NamedInheritableThreadLocal 它繼承了 InheritableThreadLocal 。還記得去年我第一次遇到開啟多線程跨服務請求的時候始終不能理解為什么這玩意能把當前線程綁定的對象暴露給子線程。前幾天 debug 了一下 InheritableThreadLocal.set() 方法恍然大悟。

    其實這個東西對 Thread、ThreadLocal 有了解就會知道,在 Thread 的構造方法里面有這樣一段代碼

    //...
    Thread parent = currentThread(); //創建子線程的時候先拿父線程
    //...
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
     this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals;
    //...

    其實我們創建子線程的時候會先拿父線程,判斷父線程里面的 inheritableThreadLocals 是不是有值,由于上面 RequestContextHolder.setRequestAttributes(xxx,true) 設置了 true ,所以父線程的 inheritableThreadLocals 是有 requestAttributes 的。這樣創建子線程后,子線程的 inheritableThreadLocals 也有值了。所以后面我們在子線程中獲取 requestAttributes 是能獲取到的。

    這樣真的解決問題了嗎?從非 web 層面來看,的確是解決了這個問題,但是在我們的 web 場景中并非如此。經過反復的測試,我們會發現子線程并不是每次都能獲取到 header ,進而我們發現了這與父子線程的結束順序有關,如果父線程早與子線程結束,那么子線程就獲取不到 header ,反之子線程能獲取到 header

    分析 inheritableRequestAttributesHolder 失效原因

    其實標題并不嚴謹,因為子線程獲取不到請求的 header 并不是因為 inheritableRequestAttributesHolder 失效。這個原因當初我也很奇怪,于是我從網上看到一篇文章,它是這么寫的。

    在源碼中ThreadLocal對象保存的是RequestAttributes attributes;這個是保存的對象的引用一旦父線程銷毀了,那RequestAttributes也會被銷毀,那RequestAttributes的引用地址的值就為null**;**雖然子線程也有RequestAttributes的引用,但是引用的值為null了。

    真的是這樣嗎??我怎么看怎么感覺不對......于是我自己驗證了下

    @GetMapping("/test")
    public void test(HttpServletRequest request) {
        RequestAttributes attr = RequestContextHolder.getRequestAttributes();
        log.info("父線程:RequestAttributes:{}", attr);
        RequestContextHolder.setRequestAttributes(attr, true);
        log.info("父線程:SpringMVC:request:{}",request);
        log.info("父線程:x-auth-token:{}",request.getHeader("x-auth-token"));
        ServletRequestAttributes attr1 = (ServletRequestAttributes) attr;
        HttpServletRequest request1 = attr1.getRequest();
        log.info("父線程:request:{}",request1);
        new Thread(
                () -> {
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    RequestAttributes childAttr = RequestContextHolder.getRequestAttributes();
                    log.info("子線程:RequestAttributes:{}",childAttr);
                    ServletRequestAttributes childServletRequestAttr = (ServletRequestAttributes) childAttr;
                    HttpServletRequest childRequest = childServletRequestAttr.getRequest();
                    log.info("子線程:childRequest:{}",childRequest);
                    String childToken = childRequest.getHeader("x-auth-token");
                    log.info("子線程:x-auth-token:{}",childToken);
                }).start();
    }

    觀察日志

    父線程:RequestAttributes:org.apache.catalina.connector.RequestFacade@ea25271
    父線程:SpringMVC:request:org.apache.catalina.connector.RequestFacade@ea25271
    父線程:x-auth-token:null
    父線程:request:org.apache.catalina.connector.RequestFacade@ea25271
    
    子線程:RequestAttributes:org.apache.catalina.connector.RequestFacade@ea25271
    子線程:childRequest:org.apache.catalina.connector.RequestFacade@ea25271
    子線程:x-auth-token:{}:null

    很明顯子線程拿到了 RequestAttitutes 對象,而且和父線程是同一個,這就推翻了上面的說法,并不是引用變為 null 了導致的。那么到底是什么原因導致父線程結束后,子線程就拿不到 request 對象里面的 header 屬性了呢?

    我們可以猜測一下,既然父線程和子線程拿到的 request 對象是同一個,并且在子線程代碼中 request 對象還不是 null,但是屬性沒了,那應該是請求結束之后某個地方對 request 對象進行了屬性移除。我們跟隨 RequestFacade 類去尋找真理,尋找尋找再尋找......終于我發現了真相在 org.apache.coyote.Request

    SpringCloud?OpenFeign服務調用傳遞token的方法

    Tomcat 內部,請求結束后會對 request 對象重置,把 header 等屬性移除,是因為這樣如果父線程提前結束,我們在子線程中才無法獲取 request 對象的 header

    或許你可以再思考一下 Tomcat 為什么要這么做?

    多線程環境下傳遞 header(二)

    既然 RequestContextHolder.setRequestAttributes(attr, true); 也不能完全實現子線程能夠獲取父線程的 header ,那么我們如何解決呢?

    控制主線程在子線程結束后再結束

    這是最簡單的方法,我把父線程掛起來,等子線程任務都執行完了,再結束父線程,這樣就不會出現子線程獲取不到 header 的情況了。最簡單的,我們可以用 ExecutorCompletionService 實現。

    重新保存 request 的 header

    上面我們已經知道了獲取不到 header 是因為 request 對象的 header 屬性被移除了,那么我們只需要自己定義一個數據結構 ThreadLocal 重新在內存中保存一份 header 屬性即可。我們可以定義一個請求攔截器,在攔截器中獲取 headers 放到自定義的結構中。

    定義結構

    public class RequestHeaderHolder {
        private static final ThreadLocal<Map<String,String>> REQUEST_HEADER_HOLDER = new InheritableThreadLocal<>(){
            @Override
            protected Map<String, String> initialValue() {
                return new HashMap<>();
            }
        };
        //...省略部分方法
    }

    攔截器

    public class RequestHeaderInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            Enumeration<String> headerNames = request.getHeaderNames();
    
            while (headerNames.hasMoreElements()){
                String s = headerNames.nextElement();
                RequestHeaderHolder.set(s,request.getHeader(s));
            }
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            RequestHeaderHolder.remove(); //注意一定要remove
        }
    }

    然后將這個攔截器添加到 InterceptorRegistry 即可。這樣我們在子線程中就可以通過 RequestHeaderHolder 獲取請求到 header

    到此,關于“SpringCloud OpenFeign服務調用傳遞token的方法”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    常德市| 资阳市| 旬邑县| 宜昌市| 额尔古纳市| 阿巴嘎旗| 英超| 陇南市| 凤城市| 太和县| 郑州市| 利辛县| 杭锦后旗| 红桥区| 达日县| 石屏县| 措美县| 沁阳市| 万荣县| 马关县| 溧阳市| 武功县| 东海县| 荔浦县| 潞城市| 全南县| 昭通市| 双城市| 万山特区| 左权县| 正镶白旗| 连云港市| 原阳县| 巴东县| 寻乌县| 峡江县| 景德镇市| 泌阳县| 齐齐哈尔市| 潼关县| 鄢陵县|