自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

SpringCloud微服務(wù)中Feign如何傳遞用戶Token,并保證多線程環(huán)境也可適用?

開發(fā) 架構(gòu)
雖然可以在異步調(diào)用時(shí)設(shè)置 RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true); 可以實(shí)現(xiàn)請求頭透傳,但是每次調(diào)用都需要加上這一句,實(shí)現(xiàn)上還略顯麻煩。

大家好,我是飄渺。

在上一篇文章中,我們解決了網(wǎng)關(guān)層認(rèn)證后向后端服務(wù)傳遞用戶信息的問題。今天我們來解決另外一個(gè)問題:如何在 OpenFeign 中傳遞 Token,并且保證多線程情況下也能適用。

這是DDD&微服務(wù)系列文章的第34篇,歡迎持續(xù)關(guān)注!

為了方便演示,首先定義一個(gè)接口,在接口中通過 Feign 調(diào)用其他服務(wù):

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

然后在遠(yuǎn)程接口中通過上文定義的UserContextHolder對(duì)象獲取用戶信息:

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

圖片圖片

通過調(diào)用結(jié)果可知,當(dāng)使用OpenFeign調(diào)用遠(yuǎn)程服務(wù)時(shí),接口是無法獲取到用戶 ID 的。

常規(guī)解決辦法

在使用OpenFeign請求其他服務(wù)接口時(shí),默認(rèn)不攜帶header信息,這樣就導(dǎo)致無法攜帶登錄用戶信息。常規(guī)情況下,我們只需要在使用 OpenFeign 調(diào)用時(shí)先從 Header 獲取 Token 信息,放入新請求即可,在項(xiàng)目中可以定義一個(gè)OpenFeign的攔截器來實(shí)現(xiàn)此功能,代碼如下所示:

public class FeignRequestConfiguration {  
  
    @Bean  
    public RequestInterceptor requestInterceptor(){  
        return new RequestInterceptor() {  
            @Override  
            public void apply(RequestTemplate template) {  
                RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();  
                ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;  
  
                // 當(dāng)主線程的請求執(zhí)行完畢后,Servlet容器會(huì)被銷毀當(dāng)前的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);  
                    }  
                }  
            }  
        };  
    }  
}

經(jīng)過上述配置以后再次調(diào)用即可在 Feign 接口中也獲取到用戶ID,如下圖所示:

圖片圖片

異步調(diào)用

上面是單線程的情況,假如我們在當(dāng)前線程中又開啟了子線程去進(jìn)行 Feign 調(diào)用,那么是無法從 RequestContextHolder 獲取到 Header 的。測試代碼如下:

public String info() {  
      
    String currentUser = UserContextHolder.getInstance().getCurrentUser();  
      
    log.info("feign調(diào)用方獲取當(dāng)前登錄用戶:" + 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("遠(yuǎn)程獲取用戶:" + info);  
  
    return currentUser;  
}

在上述代碼中,通過 CompletableFuture 開啟異步線程去調(diào)用 experimentClient ,可以發(fā)現(xiàn)此時(shí)無法獲取到用戶信息,效果如下所示:

圖片圖片

出現(xiàn)上述問題的原因是,RequestContextHolder.getRequestAttributes() 方法里面使用的一個(gè) ThreadLocal,默認(rèn)不是線程共享的,源碼如下:

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

所以主線程調(diào)用子線程時(shí),無法獲取到主線程請求里面的 RequestAttributes。

解決辦法

原因已經(jīng)清楚了,繼續(xù)觀察 RequestContextHolder.getRequestAttributes() 方法源碼,注意到如果當(dāng)前線程拿不到 RequestAttributes ,它會(huì)從 inheritableRequestAttributesHolder 里面拿,再仔細(xì)觀察發(fā)現(xiàn)源碼設(shè)置 RequestAttributes 到 ThreadLocal 的時(shí)候有這樣一個(gè)重載方法。

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

這看起來符合我們的要求,只需要在主線程調(diào)用其他線程前將 RequestAttributes 對(duì)象設(shè)置為子線程共享,就能把 Header 等信息傳遞下去。

所以,在異步調(diào)用 Feign 接口時(shí)添加如下代碼即可:

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

再次執(zhí)行發(fā)現(xiàn),是可以獲取到 userId 的。

這里使用CompletableFuture異步調(diào)用時(shí)需要使用自定義線程池,而不能使用默認(rèn)線程池ForkJoinPool,這是為什么呢?

最佳解決方案

雖然可以在異步調(diào)用時(shí)設(shè)置 RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true); 可以實(shí)現(xiàn)請求頭透傳,但是每次調(diào)用都需要加上這一句,實(shí)現(xiàn)上還略顯麻煩。

并且我們知道了獲取不到請求頭的原因是子線程無法獲取主線程的 header 屬性,那么我們只需要定義一個(gè)數(shù)據(jù)結(jié)構(gòu),使用 InheritableThreadLocal 在內(nèi)存中保存一份 header 屬性即可。在上篇文章中通過網(wǎng)關(guān)進(jìn)行 UserID 透傳時(shí)我們是使用 ThreadLocal 保存數(shù)據(jù),現(xiàn)在只需要將其換成 InheritableThreadLocal,同時(shí)在 RequestInterceptor#apply() 方法中不再通過請求頭獲取而是直接從 InheritableThreadLocal 中獲取數(shù)據(jù)。

實(shí)現(xiàn)過程如下:

1、重命名并修改數(shù)據(jù)結(jié)構(gòu):

首先,將 UserContextHolder 重命名為 RequestHeaderHolder,同時(shí)使用 InheritableThreadLocal 替換 ThreadLocal,以便子線程也能獲取數(shù)據(jù)。

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、修改請求攔截器:

將請求攔截器 UserTokenInterceptor 重命名為 RequestHeaderInterceptor,并將請求頭放入 RequestHeaderHolder 中。

@Slf4j
public 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();

        //重新設(shè)置請求頭
        while (headerNames.hasMoreElements()){
            String key = headerNames.nextElement();
            requestHeaderHolder.set(key,request.getHeader(key));
        }
        return true;
    }
    
   ......
}

3、修改 Feign 配置類:在 FeignRequestConfiguration 中不再從 RequestContextHolder 獲取數(shù)據(jù),而是從 RequestHeaderHolder 獲取數(shù)據(jù)。

@Slf4j
public 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);
                });
            }
        };
    }
}

通過上面的改造,不管是同步調(diào)用還是子線程異步調(diào)用都可以直接通過RequestHeaderHolder.getInstance().getCurrentUser();獲取用戶信息,并且調(diào)用方無須做任何改動(dòng)。

責(zé)任編輯:武曉燕 來源: JAVA日知錄
相關(guān)推薦

2024-03-06 08:36:36

2024-03-18 08:48:52

Spring多端認(rèn)證微服務(wù)

2011-06-30 17:40:07

Linux 多線程 Android

2011-06-30 17:31:32

Qt 多線程 信號(hào)

2024-07-02 10:58:53

2010-02-01 17:18:23

Python多線程環(huán)境

2022-09-06 10:29:27

無服務(wù)器Serverless

2023-03-27 15:39:53

微服務(wù)架構(gòu)REST

2023-12-14 08:01:47

環(huán)境復(fù)制微服務(wù)

2017-03-08 16:25:54

Linux多線程函數(shù)

2021-08-26 11:52:32

FeignWeb服務(wù)

2023-09-26 00:37:38

Spring微服務(wù)框架

2021-01-27 15:38:27

微服務(wù)架構(gòu)IT

2021-05-17 07:28:23

Spring可擴(kuò)展性項(xiàng)目

2024-06-04 10:05:48

微服務(wù)網(wǎng)關(guān)日志

2024-06-17 00:02:00

線程安全HashMapJDK 1.7

2023-01-26 02:07:51

HashSet線程安全

2016-10-13 15:03:27

混合云多云環(huán)境微服務(wù)

2020-12-28 11:52:36

微服務(wù)數(shù)據(jù)中臺(tái)去中心化

2018-11-28 09:53:50

游戲服務(wù)器線程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)