#feign调用丢失header数据 #自定义feign调用添加参数

场景

A 服务feign调用B服务查询数据

B服务

通过ThreadLocal获取到当前请求线程中的用户信息,

拦截器:

HttpSession session = request.getSession();
User user = (User)session.getAttribute(LOGIN_KEY);

Service

    @Override
    public List<CartItemVo> getUserCartItems() {
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
		List<Cart> li  = service.select(userInfo);
		return li;
    }


备注:
ThreadLocal
同一个线程共享数据,
比如, tomcat每次处理一个请求都会开一个线程,
从拦截器 --> Controller --> Service --> Dao --> 请求结束, 给浏览器响应
所以在同一个线程期间, 上游放了一个值, 下游也可以使用

A服务

 List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
        confirmVo.setMemberAddressVos(address);

可以看到, A服务直接远程调用B服务, 但是就是查询不到数据, 原因是:B服务被拦截器拦截, 提示用户获取时null
但是实际上用户已经登录了, 原因是:feign在远程调用的时候会自己创建request代理对象, 而且根据本项目中配置的requestInterceptor来配置这个新的request对象


Fegin远程调用丢失请求头
fegin在远程调用之前要构造请求, 调用很多的拦截器 RequestInterceptor interceptor
但是他默认构造的请求头(header)里边什么都没有
因此调用失败


以前浏览器发送请求, 请求头会自动带了cookie
但是现在是, A服务远程feign调用的B服务
这个请求是A服务, 自己创建了一个新的request, 而且这个请求里边没有任何请求头, 因此B服务从session里没取到用户信息, B服务提示用户没登录, 所以查询不到数据
所以为了解决这个问题, 就需要自己加上 feign远程调用拦截器

为了解决, 需要新增一个配置类


import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Configuration
public class FeignConfig {

    /*
    feign调用之前, 每次都会构建新的request
    而这个request的构造, 是遍历本服务内的所有requestInterceptor.apply()方法, 将这些信息统一应用到一个request中
    之后用这个request进行远程调用

    因此为了解决feign丢失请求头, 就可以自定义一个requestInterceptor,
    将本次请求的信息, 同步给这个feign新建的代理的request里
     */
    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                //每次进行feigin调用之前都会调用这个方法, 重新设置feign调用request代理对象
                System.out.println("feign远程之前会先进行RequestInterceptor.apply");
                ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = attributes.getRequest(); //老请求

                //同步请求头数据, 主要是同步cookie信息
                String cookie = request.getHeader("Cookie");
                //给新请求把老请求的cookie放进去
                requestTemplate.header("Cookie",cookie);

            }
        };
    }
}

总结

1, feign远程调用的时候, 会自己构建request, 用这个新构建的去远程调用其他服务
2, feign构建的request对象, 会遍历本项目内的所有requestInterceptor的子类, 主要是根据他的apply()方法来构建一个新的request对象, 也就是这个request有所有apply()方法的配置信息
3, 可以通过添加RequestInterceptor的对象, 来实现给feign的request对象进行动态配置
4, 不只可以加cookie, 其他的也可以搞

异步模式下feign问题

根据上边的步骤也可以看出来, 关键点在于将本服务的request所有信息都同步给feign创建的新request

而因为异步模式相当于开启新线程去做嘛,创建一个新线程, 相当于与本来同步调用的主线程不是一个线程了, 而RequestContextHolder就是通过ThreadLocal来实现的数据保存获取;

问题: 这样子的话, 异步feign调用的话, 肯定新线程就获取不到主线程request里的数据了
异步feign调用

 CompletableFuture<Void> getAdressFutrue = CompletableFuture.runAsync(() -> {
      memberFeignService.getAddress(memberRespVo.getId());
}, threadPoolExecutor);

CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
   cartFeignService.getCurrentItems();
});

这样异步调用, 因为新启线程, 和原主线程不是同一个了, 就出现问题了

  /*
        因为下边是用的异步feign调用, 异步就会启动新线程
        而新线程和主线程不在同一个, 主线程的ThreadLocal数据没办法共享到新线程
        因此, 需要先获取到主线程的请求头数据, 然后在异步任务开始之前将主线程的请求头数据共享给新线程

  */
 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

 CompletableFuture<Void> getAdressFutrue = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
 }, threadPoolExecutor);

CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> items = cartFeignService.getCurrentItems();
  
});

关键点

1, 在异步调用之前, 先将主线程的数据获取出来,
2, 在异步调用之前, 在执行方法中, (新线程创建之后), 把主线程的数据同步给新的线程

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

RequestContextHolder.setRequestAttributes(requestAttributes);
           
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值