Fegin异步情况下丢失上下文问题

问题:

Order服务需要远程调用member服务进而查询所有的地址列表,也需要远程调用cart服务进而查询购物车所有选中的购物项,串行执行远程调用会浪费大量时间,因此我们进行异步编排优化

@Override
   public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
       OrderConfirmVo confirmVo = new OrderConfirmVo();
       MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
       
       // 1、远程查询所有的地址列表
       CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
           List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
           confirmVo.setAddressVos(address);
      }, executor);

       // 2、远程查询购物车所有选中的购物项
       CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
           List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
           confirmVo.setItems(items);
      }, executor);
       // feign在远程调用请求之前要构造
       // 3、查询用户积分
       Integer integration = memberRespVo.getIntegration();
       confirmVo.setIntegration(integration);
       // 4、其他数据自动计算
       // TODO 5、防重令牌
       CompletableFuture.allOf(getAddressFuture,cartFuture).get();
       return confirmVo;
  }

开启异步时获取不到老请求的信息即拦截器中会发生空指针的错误,即老request对象获取不到,是null

@Configuration
public class GulimallFeignConfig {
   /**
    * feign在远程调用之前会执行所有的RequestInterceptor拦截器
    *
    * @return
    */
   @Bean("requestInterceptor")
   public RequestInterceptor requestInterceptor() {
       return new RequestInterceptor() {
           @Override
           public void apply(RequestTemplate requestTemplate) {
               // 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
               ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
               HttpServletRequest request = attributes.getRequest();
               // 2、同步请求头数据,Cookie
               String cookie = request.getHeader("Cookie");
               // 给新请求同步了老请求的cookie
              requestTemplate.header("Cookie", cookie);  
          }
      };
  }
}

原因:

@Configuration
public class GulimallFeignConfig {
   /**
    * feign在远程调用之前会执行所有的RequestInterceptor拦截器
    *
    * @return
    */
   @Bean("requestInterceptor")
   public RequestInterceptor requestInterceptor() {
       return new RequestInterceptor() {
           @Override
           public void apply(RequestTemplate requestTemplate) {
               // 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
               ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
               System.out.println("RequestInterceptor线程...."+Thread.currentThread().getId());
               if (attributes != null) {
                   HttpServletRequest request = attributes.getRequest();
                   // 2、同步请求头数据,Cookie
                   String cookie = request.getHeader("Cookie");
                   // 给新请求同步了老请求的cookie
                   requestTemplate.header("Cookie", cookie);
              }
          }
      };
  }
}
@Override
   public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
       OrderConfirmVo confirmVo = new OrderConfirmVo();
       MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
       System.out.println("主线程线程...."+Thread.currentThread().getId());
       // 1、远程查询所有的地址列表
       CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
           System.out.println("member线程...."+Thread.currentThread().getId());
           List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
           confirmVo.setAddressVos(address);
      }, executor);

       // 2、远程查询购物车所有选中的购物项
       CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
           System.out.println("cart线程...."+Thread.currentThread().getId());
           List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
           confirmVo.setItems(items);
      }, executor);
       // feign在远程调用请求之前要构造
       // 3、查询用户积分
       Integer integration = memberRespVo.getIntegration();
       confirmVo.setIntegration(integration);
       // 4、其他数据自动计算
       // TODO 5、防重令牌
       CompletableFuture.allOf(getAddressFuture,cartFuture).get();
       return confirmVo;
  }

测试打印线程:

 

我们给feign的拦截器所拦截的请求加上了请求头,但这个获取请求头的过程是在一个线程执行过程中进行的,但是由于 RequestContextHolder底层使用的是线程共享数据 ThreadLocal<RequestAttributes>,线程共享数据的域是当前线程下,线程之间是不共享的

 

解决:

向 RequestContextHolder线程域中放主线程的请求域。

即先在主线程中获取到请求头相关信息(RequestContextHolder.getRequestAttributes();),然后在异步调用的过程中将情求头加到正在执行异步操作的线程中( RequestContextHolder.setRequestAttributes(requestAttributes);),这样请求头就不会丢失。

@Override
   public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
       OrderConfirmVo confirmVo = new OrderConfirmVo();
       MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
       // 获取主线程的域,主线程中获取请求头信息
       RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
       // 1、远程查询所有的地址列表
       CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
           RequestContextHolder.setRequestAttributes(requestAttributes);
           // 将主线程的域放在该线程的域中
           List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
           confirmVo.setAddressVos(address);
      }, executor);

       // 2、远程查询购物车所有选中的购物项
       CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
           // 将老请求的域放在该线程的域中
           RequestContextHolder.setRequestAttributes(requestAttributes);
           List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
           confirmVo.setItems(items);
      }, executor);
       // feign在远程调用请求之前要构造
       // 3、查询用户积分
       Integer integration = memberRespVo.getIntegration();
       confirmVo.setIntegration(integration);
       // 4、其他数据自动计算
       // TODO 5、防重令牌
       CompletableFuture.allOf(getAddressFuture,cartFuture).get();
       return confirmVo;
  }

虽然都是同一个 RequestContextHolder类在调用方法,但是 RequestContextHolder在不同线程中的意义是不同的,它仅代表当前线程。

@Configuration
public class GulimallFeignConfig {
   /**
    * feign在远程调用之前会执行所有的RequestInterceptor拦截器
    *
    * @return
    */
   @Bean("requestInterceptor")
   public RequestInterceptor requestInterceptor() {
       return new RequestInterceptor() {
           @Override
           public void apply(RequestTemplate requestTemplate) {
               // 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
               ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
               if (attributes != null) {
                   HttpServletRequest request = attributes.getRequest();
                   // 2、同步请求头数据,Cookie
                   String cookie = request.getHeader("Cookie");
                   // 给新请求同步了老请求的cookie
                   requestTemplate.header("Cookie", cookie);
              }
          }
      };
  }
}
 
posted @   小謝同學  阅读(236)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示