Feign异步远程调用丢失请求头的两种解决方法

1、为了提高代码执行效率,将获取订单确认页所需数据相关代码改为异步形式

@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
    OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
    MemberRespTo loginUser = OrderInterceptor.loginUser.get();
    CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
        List<MemberAddressVo> address = memberFeignService.getAddress(loginUser.getId());
        orderConfirmVo.setAddress(address);
    }, executor);
    CompletableFuture<Void> itemsFuture = CompletableFuture.runAsync(() -> {
        List<OrderItemVo> items = cartFeignService.getUserCartItems();
        orderConfirmVo.setItems(items);
    }, executor);
    orderConfirmVo.setIntegration(loginUser.getIntegration());
    CompletableFuture.allOf(addressFuture, itemsFuture).get();
    return orderConfirmVo;
}

2、重启运行后报错

img

3、单步调试后发现错误原因是:没有在当前线程的上下文中获取请求的相关属性

img

4、由代码可知,请求信息是从requestAttributesHolder 中获取的,而 requestAttributesHolder 类型为 ThreadLocal,即本地线程。异步任务直接使用线程池里的线程去执行方法,而不是继续使用原有线程,导致无法继续使用原线程中的数据

public abstract class RequestContextHolder {
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
    @Nullable
    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
        if (attributes == null) {
            attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }
}

img

5、解决方法:

5-1、在每次异步任务执行前,向线程中共享之前的请求数据

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
    RequestContextHolder.setRequestAttributes(requestAttributes);
    List<MemberAddressVo> address = memberFeignService.getAddress(loginUser.getId());
    orderConfirmVo.setAddress(address);
}, executor);
CompletableFuture<Void> itemsFuture = CompletableFuture.runAsync(() -> {
    RequestContextHolder.setRequestAttributes(requestAttributes);
    List<OrderItemVo> items = cartFeignService.getUserCartItems();
    orderConfirmVo.setItems(items);
}, executor);

5-2、使用 InheritableThreadLocal 类

5-2-1、ThreadLocal 的子类 InheritableThreadLocal 可以使让子线程继承父线程的信息。根据代码,当参数 inheritable 为 true 时,子线程可以共享父线程信息,系统默认 inheritable 为 false

public abstract class RequestContextHolder {
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
    public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
        if (attributes == null) {
            resetRequestAttributes();
        } else if (inheritable) {
            inheritableRequestAttributesHolder.set(attributes);
            requestAttributesHolder.remove();
        } else {
            requestAttributesHolder.set(attributes);
            inheritableRequestAttributesHolder.remove();
        }
    }
}

5-2-2、因此,可以通过语句,在异步调用前将父线程中的请求信息绑定给子线程

RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
    List<MemberAddressVo> address = memberFeignService.getAddress(loginUser.getId());
    orderConfirmVo.setAddress(address);
}, executor);
CompletableFuture<Void> itemsFuture = CompletableFuture.runAsync(() -> {
    List<OrderItemVo> items = cartFeignService.getUserCartItems();
    orderConfirmVo.setItems(items);
}, executor);

  • 9
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值