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

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

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

業(yè)務場景

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

  • gateway 就解析用戶的 token 然后路由的時候把 userId 等相關信息添加到 header 中傳遞下去。
  • gateway 直接把 token 傳遞下去,每個子微服務自己在過濾器解析 token

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

RequestInterceptor

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

/** Feign 調(diào)用的時候傳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)關傳過來的 token
    if (StringUtils.hasText(token)) {
      template.header("X-AUTH-TOKEN", token);
    }
  }
}

然后在 @FeignClient 中使用

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

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

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

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

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

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

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

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

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

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

分析 inheritableRequestAttributesHolder 原理

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

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

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

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

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

分析 inheritableRequestAttributesHolder 失效原因

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

在源碼中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 對象,而且和父線程是同一個,這就推翻了上面的說法,并不是引用變?yōu)?null 了導致的。那么到底是什么原因導致父線程結束后,子線程就拿不到 request 對象里面的 header 屬性了呢?

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

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

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

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

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

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

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

重新保存 request 的 header

上面我們已經(jīng)知道了獲取不到 header 是因為 request 對象的 header 屬性被移除了,那么我們只需要自己定義一個數(shù)據(jù)結構 ThreadLocal 重新在內(nèi)存中保存一份 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 。

結語

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

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

相關文章

  • 一文快速掌握Spring?Cloud?Stream

    一文快速掌握Spring?Cloud?Stream

    這篇文章主要介紹了Spring?Cloud?Stream詳解,本篇文章所涉及到的demo練習使用的cloud?2021.0.3+?springboot2.6.8,通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-08-08
  • 一步步教你寫一個SpringMVC框架

    一步步教你寫一個SpringMVC框架

    現(xiàn)在主流的Web MVC框架除了Struts這個主力外,其次就是Spring MVC了,因此這也是作為一名程序員需要掌握的主流框架,這篇文章主要給大家介紹了關于如何一步步寫一個SpringMVC框架的相關資料,需要的朋友可以參考下
    2022-03-03
  • SpringBoot 實戰(zhàn) 之 優(yōu)雅終止服務的方法

    SpringBoot 實戰(zhàn) 之 優(yōu)雅終止服務的方法

    本篇文章主要介紹了SpringBoot 實戰(zhàn) 之 優(yōu)雅終止服務的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • 深入理解Spring Boot屬性配置文件

    深入理解Spring Boot屬性配置文件

    這篇文章主要給大家深入的介紹了關于Spring Boot屬性配置文件的相關資料,文中介紹的很詳細,相信對大家具有一定的參考借鑒價值,需要的朋友們下面來一起看看吧。
    2017-02-02
  • java開發(fā)AOP面向切面編程入門

    java開發(fā)AOP面向切面編程入門

    這篇文章主要介紹了java開發(fā)的AOP面向切面編程入門的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步學有所得
    2021-10-10
  • Java 8 Function函數(shù)式接口及函數(shù)式接口實例

    Java 8 Function函數(shù)式接口及函數(shù)式接口實例

    函數(shù)式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。接下來通過本文給大家介紹Java 8 Function函數(shù)式接口及函數(shù)式接口實例代碼,需要的朋友可以參考下
    2018-05-05
  • Java去除字符串空格的幾種方法簡單舉例

    Java去除字符串空格的幾種方法簡單舉例

    在Java中要去掉字符串中的空格,可以使用多種方法,這篇文章主要給大家介紹了關于Java去除字符串空格的幾種方法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2023-12-12
  • Java Swing GroupLayout分組布局的實現(xiàn)代碼

    Java Swing GroupLayout分組布局的實現(xiàn)代碼

    這篇文章主要介紹了Java Swing GroupLayout分組布局的實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-12-12
  • Java實現(xiàn)多對多網(wǎng)絡通訊的流程

    Java實現(xiàn)多對多網(wǎng)絡通訊的流程

    這篇文章主要介紹了Java實現(xiàn)多對多網(wǎng)絡通訊的流程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • springboot如何獲取application.yml里值的方法

    springboot如何獲取application.yml里值的方法

    這篇文章主要介紹了springboot如何獲取application.yml里的值,文章圍繞主題相關自資料展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-04-04

最新評論

沙雅县| 巴中市| 会泽县| 满洲里市| 南充市| 四川省| 紫金县| 正阳县| 桦南县| 兰西县| 青海省| 斗六市| 清镇市| 柘荣县| 姜堰市| 弥渡县| 虎林市| 安宁市| 马鞍山市| 丹棱县| 密云县| 老河口市| 项城市| 建湖县| 辛集市| 康乐县| 廊坊市| 河池市| 聂荣县| 泽普县| 青海省| 阿尔山市| 武乡县| 石家庄市| 遂昌县| 洪洞县| 吕梁市| 湘西| 西贡区| 鸡西市| 岢岚县|