当前位置:首页 > 科技  > 软件

SpringCloud微服务中Feign如何传递用户Token,并保证多线程环境也可适用?

来源: 责编: 时间:2024-03-27 09:23:21 317观看
导读大家好,我是飘渺。在上一篇文章中,我们解决了网关层认证后向后端服务传递用户信息的问题。今天我们来解决另外一个问题:如何在 OpenFeign 中传递 Token,并且保证多线程情况下也能适用。这是DDD&微服务系列文章的第34篇,欢

大家好,我是飘渺。5oP28资讯网——每日最新资讯28at.com

在上一篇文章中,我们解决了网关层认证后向后端服务传递用户信息的问题。今天我们来解决另外一个问题:如何在 OpenFeign 中传递 Token,并且保证多线程情况下也能适用。5oP28资讯网——每日最新资讯28at.com

这是DDD&微服务系列文章的第34篇,欢迎持续关注!5oP28资讯网——每日最新资讯28at.com

为了方便演示,首先定义一个接口,在接口中通过 Feign 调用其他服务:5oP28资讯网——每日最新资讯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对象获取用户信息:5oP28资讯网——每日最新资讯28at.com

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

5oP28资讯网——每日最新资讯28at.com

图片图片5oP28资讯网——每日最新资讯28at.com

通过调用结果可知,当使用OpenFeign调用远程服务时,接口是无法获取到用户 ID 的。5oP28资讯网——每日最新资讯28at.com

常规解决办法

在使用OpenFeign请求其他服务接口时,默认不携带header信息,这样就导致无法携带登录用户信息。常规情况下,我们只需要在使用 OpenFeign 调用时先从 Header 获取 Token 信息,放入新请求即可,在项目中可以定义一个OpenFeign的拦截器来实现此功能,代码如下所示:5oP28资讯网——每日最新资讯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,如下图所示:5oP28资讯网——每日最新资讯28at.com

图片图片5oP28资讯网——每日最新资讯28at.com

异步调用

上面是单线程的情况,假如我们在当前线程中又开启了子线程去进行 Feign 调用,那么是无法从 RequestContextHolder 获取到 Header 的。测试代码如下:5oP28资讯网——每日最新资讯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 ,可以发现此时无法获取到用户信息,效果如下所示:5oP28资讯网——每日最新资讯28at.com

图片图片5oP28资讯网——每日最新资讯28at.com

5oP28资讯网——每日最新资讯28at.com

出现上述问题的原因是,RequestContextHolder.getRequestAttributes() 方法里面使用的一个 ThreadLocal,默认不是线程共享的,源码如下:5oP28资讯网——每日最新资讯28at.com

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

所以主线程调用子线程时,无法获取到主线程请求里面的 RequestAttributes。5oP28资讯网——每日最新资讯28at.com

解决办法

原因已经清楚了,继续观察 RequestContextHolder.getRequestAttributes() 方法源码,注意到如果当前线程拿不到 RequestAttributes ,它会从 inheritableRequestAttributesHolder 里面拿,再仔细观察发现源码设置 RequestAttributes 到 ThreadLocal 的时候有这样一个重载方法。5oP28资讯网——每日最新资讯28at.com

/** * 给当前线程绑定属性 * @param inheritable 是否要将属性暴露给子线程 */public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {      ......}

这看起来符合我们的要求,只需要在主线程调用其他线程前将 RequestAttributes 对象设置为子线程共享,就能把 Header 等信息传递下去。5oP28资讯网——每日最新资讯28at.com

所以,在异步调用 Feign 接口时添加如下代码即可:5oP28资讯网——每日最新资讯28at.com

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

再次执行发现,是可以获取到 userId 的。5oP28资讯网——每日最新资讯28at.com

这里使用CompletableFuture异步调用时需要使用自定义线程池,而不能使用默认线程池ForkJoinPool,这是为什么呢?5oP28资讯网——每日最新资讯28at.com

最佳解决方案

虽然可以在异步调用时设置 RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true); 可以实现请求头透传,但是每次调用都需要加上这一句,实现上还略显麻烦。5oP28资讯网——每日最新资讯28at.com

并且我们知道了获取不到请求头的原因是子线程无法获取主线程的 header 属性,那么我们只需要定义一个数据结构,使用 InheritableThreadLocal 在内存中保存一份 header 属性即可。在上篇文章中通过网关进行 UserID 透传时我们是使用 ThreadLocal 保存数据,现在只需要将其换成 InheritableThreadLocal,同时在 RequestInterceptor#apply() 方法中不再通过请求头获取而是直接从 InheritableThreadLocal 中获取数据。5oP28资讯网——每日最新资讯28at.com

实现过程如下:5oP28资讯网——每日最新资讯28at.com

1、重命名并修改数据结构:5oP28资讯网——每日最新资讯28at.com

首先,将 UserContextHolder 重命名为 RequestHeaderHolder,同时使用 InheritableThreadLocal 替换 ThreadLocal,以便子线程也能获取数据。5oP28资讯网——每日最新资讯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、修改请求拦截器:5oP28资讯网——每日最新资讯28at.com

将请求拦截器 UserTokenInterceptor 重命名为 RequestHeaderInterceptor,并将请求头放入 RequestHeaderHolder 中。5oP28资讯网——每日最新资讯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 获取数据。5oP28资讯网——每日最新资讯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();获取用户信息,并且调用方无须做任何改动。5oP28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-79599-0.htmlSpringCloud微服务中Feign如何传递用户Token,并保证多线程环境也可适用?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 你的Css选择器可视化备忘录

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

标签:
  • 热门焦点
  • 5月安卓手机好评榜:魅族20 Pro夺冠

    性能榜和性价比榜之后,我们来看最后的安卓手机好评榜,数据来源安兔兔评测,收集时间2023年5月1日至5月31日,仅限国内市场。第一名:魅族20 Pro好评率:97.50%不得不感慨魅族老品牌还
  • 印度登月最关键一步!月船三号今晚进入环月轨道

    8月5日消息,据印度官方消息,月船三号将于北京时间今晚21时30分左右开始近月制动进入环月轨道。这是该探测器能够成功的最关键步骤之一,如果成功将开始围
  • 学习JavaScript的10个理由...

    作者 | Simplilearn编译 | 王瑞平当你决心学习一门语言的时候,很难选择到底应该学习哪一门,常用的语言有Python、Java、JavaScript、C/CPP、PHP、Swift、C#、Ruby、Objective-
  • 签约井川里予、何丹彤,单视频点赞近千万,MCN黑马永恒文希快速崛起!

    来源:视听观察永恒文希传媒作为一家MCN公司,说起它的名字来,可能大家会觉得有点儿陌生,但是说出来下面一串的名字之后,或许大家就会感到震惊,原来这么多网红,都签约这家公司了。根
  • 滴滴违法违规被罚80.26亿 共存在16项违法事实

    滴滴违法违规被罚80.26亿 存在16项违法事实开始于2121年7月,历经一年时间,网络安全审查办公室对“滴滴出行”网络安全审查终于有了一个暂时的结束。据“网信
  • 2022爆款:ROG魔霸6 冰川散热系统持续护航

    喜逢开学季,各大商家开始推出自己的新产品,进行打折促销活动。对于忠实的端游爱好者来说,能够拥有一款梦寐以求的笔记本电脑是一件十分开心的事。但是现在的
  • 上海举办人工智能大会活动,建设人工智能新高地

    人工智能大会在上海浦江两岸隆重拉开帷幕,人工智能新技术、新产品、新应用、新理念集中亮相。8月30日晚,作为大会的特色活动之一的上海人工智能发展盛典人工
  • “买真退假” 这种“羊毛”不能薅

    □ 法治日报 记者 王春   □ 本报通讯员 胡佳丽  2020年初,还在上大学的小东加入了一个大学生兼职QQ群。群主&ldquo;七王&rdquo;在群里介绍一些刷单赚
  • 北京:科技教育体验基地开始登记

      北京“科技馆之城”科技教育体验基地登记和认证工作日前启动。首批北京科技教育体验基地拟于2023年全国科普日期间挂牌,后续还将开展常态化登记。  北京科技教育体验基
Top