欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringCloud?OpenFeign?服務(wù)調(diào)用傳遞?token的場(chǎng)景分析

 更新時(shí)間:2022年07月26日 11:50:35   作者:暮色妖嬈丶  
這篇文章主要介紹了SpringCloud?OpenFeign?服務(wù)調(diào)用傳遞?token的場(chǎng)景分析,本篇文章簡(jiǎn)單介紹?OpenFeign?調(diào)用傳遞?header?,以及多線(xiàn)程環(huán)境下可能會(huì)出現(xiàn)的問(wèn)題,其中涉及到?ThreadLocal?的相關(guān)知識(shí),需要的朋友可以參考下

業(yè)務(wù)場(chǎng)景

通常微服務(wù)對(duì)于用戶(hù)認(rèn)證信息解析有兩種方案

  • gateway 就解析用戶(hù)的 token 然后路由的時(shí)候把 userId 等相關(guān)信息添加到 header 中傳遞下去。
  • gateway 直接把 token 傳遞下去,每個(gè)子微服務(wù)自己在過(guò)濾器解析 token

現(xiàn)在有一個(gè)從 A 服務(wù)調(diào)用 B 服務(wù)接口的內(nèi)部調(diào)用業(yè)務(wù)場(chǎng)景,無(wú)論是哪種方案我們都需要把 header 從 A 服務(wù)傳遞到 B 服務(wù)。

RequestInterceptor

OpenFeign 給我們提供了一個(gè)請(qǐng)求攔截器 RequestInterceptor ,我們可以實(shí)現(xiàn)這個(gè)接口重寫(xiě) apply 方法將當(dāng)前請(qǐng)求的 header 添加到請(qǐng)求中去,傳遞給下游服務(wù),RequestContextHolder 可以獲得當(dāng)前線(xiàn)程綁定的 Request 對(duì)象

/** Feign 調(diào)用的時(shí)候傳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");//網(wǎng)關(guān)傳過(guò)來(lái)的 token
    if (StringUtils.hasText(token)) {
      template.header("X-AUTH-TOKEN", token);
    }
  }
}

然后在 @FeignClient 中使用

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

多線(xiàn)程環(huán)境下傳遞 header(一)

上面是單線(xiàn)程的情況,假如我們?cè)诋?dāng)前線(xiàn)程中又開(kāi)啟了子線(xiàn)程去進(jìn)行 Feign 調(diào)用,那么是無(wú)法從 RequestContextHolder 獲取到 header 的,原因很簡(jiǎn)單,看下 RequestContextHolder 源碼就知道了,它里面是一個(gè) ThreadLocal ,線(xiàn)程都變了,那肯定獲取不到主線(xiàn)程請(qǐng)求里面的 requestAttribute 了。

原因已經(jīng)清楚了,現(xiàn)在想辦法去解決它。觀(guān)察 RequestContextHolder.getRequestAttributes() 方法源碼

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

注意到如果當(dāng)前線(xiàn)程拿不到 RequestAttributes ,他會(huì)從 inheritableRequestAttributesHolder 里面拿,再仔細(xì)觀(guān)察發(fā)現(xiàn)源碼設(shè)置 RequestAttributesThreadLocal 的時(shí)候有這樣一個(gè)重載方法

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

這特喵的完美符合我們的需求,現(xiàn)在我們的問(wèn)題就是子線(xiàn)程沒(méi)有拿到主線(xiàn)程的 RequestContextHolder 里面的屬性。在業(yè)務(wù)代碼中:

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

開(kāi)發(fā)環(huán)境測(cè)試之后發(fā)現(xiàn)子線(xiàn)程已經(jīng)能夠從 RequestContextHolder 拿到主線(xiàn)程的請(qǐng)求對(duì)象了。

分析 inheritableRequestAttributesHolder 原理

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

其實(shí)這個(gè)東西對(duì) Thread、ThreadLocal 有了解就會(huì)知道,在 Thread 的構(gòu)造方法里面有這樣一段代碼

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

其實(shí)我們創(chuàng)建子線(xiàn)程的時(shí)候會(huì)先拿父線(xiàn)程,判斷父線(xiàn)程里面的 inheritableThreadLocals 是不是有值,由于上面 RequestContextHolder.setRequestAttributes(xxx,true) 設(shè)置了 true ,所以父線(xiàn)程的 inheritableThreadLocals 是有 requestAttributes 的。這樣創(chuàng)建子線(xiàn)程后,子線(xiàn)程的 inheritableThreadLocals 也有值了。所以后面我們?cè)谧泳€(xiàn)程中獲取 requestAttributes 是能獲取到的。

這樣真的解決問(wèn)題了嗎?從非 web 層面來(lái)看,的確是解決了這個(gè)問(wèn)題,但是在我們的 web 場(chǎng)景中并非如此。經(jīng)過(guò)反復(fù)的測(cè)試,我們會(huì)發(fā)現(xiàn)子線(xiàn)程并不是每次都能獲取到 header ,進(jìn)而我們發(fā)現(xiàn)了這與父子線(xiàn)程的結(jié)束順序有關(guān),如果父線(xiàn)程早與子線(xiàn)程結(jié)束,那么子線(xiàn)程就獲取不到 header ,反之子線(xiàn)程能獲取到 header。

分析 inheritableRequestAttributesHolder 失效原因

其實(shí)標(biāo)題并不嚴(yán)謹(jǐn),因?yàn)樽泳€(xiàn)程獲取不到請(qǐng)求的 header 并不是因?yàn)?inheritableRequestAttributesHolder 失效。這個(gè)原因當(dāng)初我也很奇怪,于是我從網(wǎng)上看到一篇文章,它是這么寫(xiě)的。

在源碼中ThreadLocal對(duì)象保存的是RequestAttributes attributes;這個(gè)是保存的對(duì)象的引用。一旦父線(xiàn)程銷(xiāo)毀了,那RequestAttributes也會(huì)被銷(xiāo)毀,那RequestAttributes的引用地址的值就為null**;**雖然子線(xiàn)程也有RequestAttributes的引用,但是引用的值為null了。

真的是這樣嗎??我怎么看怎么感覺(jué)不對(duì)......于是我自己驗(yàn)證了下

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

觀(guān)察日志

父線(xiàn)程:RequestAttributes:org.apache.catalina.connector.RequestFacade@ea25271
父線(xiàn)程:SpringMVC:request:org.apache.catalina.connector.RequestFacade@ea25271
父線(xiàn)程:x-auth-token:null
父線(xiàn)程:request:org.apache.catalina.connector.RequestFacade@ea25271

子線(xiàn)程:RequestAttributes:org.apache.catalina.connector.RequestFacade@ea25271
子線(xiàn)程:childRequest:org.apache.catalina.connector.RequestFacade@ea25271
子線(xiàn)程:x-auth-token:{}:null

很明顯子線(xiàn)程拿到了 RequestAttitutes 對(duì)象,而且和父線(xiàn)程是同一個(gè),這就推翻了上面的說(shuō)法,并不是引用變?yōu)?null 了導(dǎo)致的。那么到底是什么原因?qū)е赂妇€(xiàn)程結(jié)束后,子線(xiàn)程就拿不到 request 對(duì)象里面的 header 屬性了呢?

我們可以猜測(cè)一下,既然父線(xiàn)程和子線(xiàn)程拿到的 request 對(duì)象是同一個(gè),并且在子線(xiàn)程代碼中 request 對(duì)象還不是 null,但是屬性沒(méi)了,那應(yīng)該是請(qǐng)求結(jié)束之后某個(gè)地方對(duì) request 對(duì)象進(jìn)行了屬性移除。我們跟隨 RequestFacade 類(lèi)去尋找真理,尋找尋找再尋找......終于我發(fā)現(xiàn)了真相在 org.apache.coyote.Request 類(lèi)

Tomcat 內(nèi)部,請(qǐng)求結(jié)束后會(huì)對(duì) request 對(duì)象重置,把 header 等屬性移除,是因?yàn)檫@樣如果父線(xiàn)程提前結(jié)束,我們?cè)谧泳€(xiàn)程中才無(wú)法獲取 request 對(duì)象的 header 。

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

多線(xiàn)程環(huán)境下傳遞 header(二)

既然 RequestContextHolder.setRequestAttributes(attr, true); 也不能完全實(shí)現(xiàn)子線(xiàn)程能夠獲取父線(xiàn)程的 header ,那么我們?nèi)绾谓鉀Q呢?

控制主線(xiàn)程在子線(xiàn)程結(jié)束后再結(jié)束

這是最簡(jiǎn)單的方法,我把父線(xiàn)程掛起來(lái),等子線(xiàn)程任務(wù)都執(zhí)行完了,再結(jié)束父線(xiàn)程,這樣就不會(huì)出現(xiàn)子線(xiàn)程獲取不到 header 的情況了。最簡(jiǎn)單的,我們可以用 ExecutorCompletionService 實(shí)現(xiàn)。

重新保存 request 的 header

上面我們已經(jīng)知道了獲取不到 header 是因?yàn)?request 對(duì)象的 header 屬性被移除了,那么我們只需要自己定義一個(gè)數(shù)據(jù)結(jié)構(gòu) ThreadLocal 重新在內(nèi)存中保存一份 header 屬性即可。我們可以定義一個(gè)請(qǐng)求攔截器,在攔截器中獲取 headers 放到自定義的結(jié)構(gòu)中。

定義結(jié)構(gòu)

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
    }
}

然后將這個(gè)攔截器添加到 InterceptorRegistry 即可。這樣我們?cè)谧泳€(xiàn)程中就可以通過(guò) RequestHeaderHolder 獲取請(qǐng)求到 header 。

結(jié)語(yǔ)

本篇文章簡(jiǎn)單介紹 OpenFeign 調(diào)用傳遞 header ,以及多線(xiàn)程環(huán)境下可能會(huì)出現(xiàn)的問(wèn)題。其中涉及到 ThreadLocal 的相關(guān)知識(shí),如果有同學(xué)對(duì) ThreadLocal、InheritableThreadLocal 不清楚的可以留言,后面出一篇 ThreadLocal 的文章。

到此這篇關(guān)于SpringCloud OpenFeign 服務(wù)調(diào)用傳遞 token的場(chǎng)景分析的文章就介紹到這了,更多相關(guān)SpringCloud OpenFeign傳遞 token內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 淺談Java設(shè)計(jì)模式之七大設(shè)計(jì)原則

    淺談Java設(shè)計(jì)模式之七大設(shè)計(jì)原則

    在此之前,我已經(jīng)寫(xiě)過(guò)很多篇關(guān)于設(shè)計(jì)模式的文章.但都比較草草的理解和簡(jiǎn)單的實(shí)現(xiàn),并未深入理解.為了更加深入感受Java設(shè)計(jì)的魅力,編程的藝術(shù),今天進(jìn)行了七大設(shè)計(jì)原則的學(xué)習(xí)理解,后續(xù)進(jìn)行23種設(shè)計(jì)模式的深入學(xué)習(xí)探究,需要的朋友可以參考下
    2021-05-05
  • 使用CI/CD工具Github Action發(fā)布jar到Maven中央倉(cāng)庫(kù)的詳細(xì)介紹

    使用CI/CD工具Github Action發(fā)布jar到Maven中央倉(cāng)庫(kù)的詳細(xì)介紹

    今天通過(guò)對(duì)Github Action的簡(jiǎn)單使用來(lái)介紹了CI/CD的作用,這個(gè)技術(shù)體系是項(xiàng)目集成交付的趨勢(shì),也是面試中的一個(gè)亮點(diǎn)技能。 而且這種方式可以實(shí)現(xiàn)“一次配置,隨時(shí)隨地集成部署”,感興趣的朋友一起看看吧
    2021-07-07
  • java中對(duì)象轉(zhuǎn)json字符串的幾種常用方式舉例

    java中對(duì)象轉(zhuǎn)json字符串的幾種常用方式舉例

    這篇文章主要給大家介紹了關(guān)于java中對(duì)象轉(zhuǎn)json字符串的幾種常用方式,在Java中可以使用許多庫(kù)將對(duì)象轉(zhuǎn)換為JSON字符串,其中最常用的是Jackson和Gson,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-10-10
  • 全面詳解Spring?Bean生命周期教程示例

    全面詳解Spring?Bean生命周期教程示例

    這篇文章主要為大家介紹了Spring?Bean生命周期的全面詳解教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • Java中controller層如何接收帶參數(shù)的查詢(xún)

    Java中controller層如何接收帶參數(shù)的查詢(xún)

    本文主要介紹了Java中controller層如何接收帶參數(shù)的查詢(xún),在控制器層接收帶參數(shù)的查詢(xún)可以通過(guò)多種方式實(shí)現(xiàn),下面就詳細(xì)的介紹一下,感興趣的可以了解一下
    2023-08-08
  • 如何獲取所有spring管理的bean

    如何獲取所有spring管理的bean

    這篇文章主要介紹了如何獲取所有spring管理的bean,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java解決青蛙跳臺(tái)階問(wèn)題流程

    Java解決青蛙跳臺(tái)階問(wèn)題流程

    所謂的青蛙跳臺(tái)階問(wèn)題,就是指一只青蛙一次可以跳上1級(jí)臺(tái)階,也可以跳上2級(jí)。求該青蛙跳上一個(gè)n級(jí)的臺(tái)階總共有多少種跳法。本文將用Java解決這一問(wèn)題,需要的可以參考一下
    2022-03-03
  • JAVA內(nèi)存模型和Happens-Before規(guī)則知識(shí)點(diǎn)講解

    JAVA內(nèi)存模型和Happens-Before規(guī)則知識(shí)點(diǎn)講解

    在本篇文章里小編給大家整理的是一篇關(guān)于JAVA內(nèi)存模型和Happens-Before規(guī)則知識(shí)點(diǎn)內(nèi)容,有需要的朋友們跟著學(xué)習(xí)下。
    2020-11-11
  • Spring實(shí)現(xiàn)擁有者權(quán)限驗(yàn)證的方法示例

    Spring實(shí)現(xiàn)擁有者權(quán)限驗(yàn)證的方法示例

    這篇文章主要介紹了Spring實(shí)現(xiàn)擁有者權(quán)限驗(yàn)證的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • 淺談Spring中如何使用設(shè)計(jì)模式

    淺談Spring中如何使用設(shè)計(jì)模式

    這篇文章主要介紹了淺談Spring中如何使用設(shè)計(jì)模式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05

最新評(píng)論