谷粒商城实战笔记-问题记录-Feign异步调用丢失请求头问题

文章目录

在请求多个信息时,我们使用了多线程,这就带来了一个问题,前面我们解决Feign丢失请求头的方案在多线程下,不再有效,丢失请求头的问题再度出现。

单线程下生效的原理

  1. 请求处理流程

    在单线程环境下,请求的处理流程是顺序的。当一个请求到达时,它会被控制器(Controller)处理。控制器会调用服务(Service)来处理业务逻辑。在这个过程中,请求相关的数据会被存储在ThreadLocal中。

  2. RequestContextHolder
    RequestContextHolder是一个工具类,它提供了获取当前请求相关数据的方法。在单线程环境下,当一个请求被处理时,RequestContextHolder.getRequestAttributes()会返回当前请求的ServletRequestAttributes。这个ServletRequestAttributes包含了请求相关的数据,包括请求头。

  3. RequestInterceptor

    当Feign客户端发送请求时,RequestInterceptorapply方法会被调用。在这个方法中,通过RequestContextHolder.getRequestAttributes()获取当前请求的ServletRequestAttributes,然后将请求头添加到新的请求中。由于在单线程环境下,请求的处理是顺序的,因此RequestInterceptor能够正确地获取到请求头,并将其添加到新的请求中。

多线程下Interceptor不生效的原因

  1. 多线程环境

    在多线程环境下,每个线程都有自己的请求上下文。当一个线程处理请求时,它会将请求相关的数据存储在ThreadLocal中。然而,当这个线程处理完请求后,它会释放这些数据,以便其他线程可以使用ThreadLocal来存储自己的请求数据。

  2. RequestContextHolder

    在多线程环境下,当一个线程处理完请求后,它会释放ThreadLocal中的请求数据。这意味着,当另一个线程处理请求时,它不会访问到前一个线程的请求数据,包括请求头。因此,即使使用了RequestInterceptor,多线程下还是会丢失header头。

  3. Controller和Service是否在同一线程

    在多线程环境下,控制器(Controller)和服务(Service)可能不在同一个线程中。当控制器处理请求时,它会将请求相关的数据存储在ThreadLocal中。然而,当控制器调用服务来处理业务逻辑时,这个请求数据可能会被释放,以便其他线程可以使用ThreadLocal来存储自己的请求数据。因此,即使服务在同一个线程中,它也可能无法访问到控制器的请求数据,包括请求头。

通过以上分析,单线程下生效的原理在于请求的处理是顺序的,RequestInterceptor能够正确地获取到请求头,并将其添加到新的请求中。而多线程下Interceptor不生效的原因在于ThreadLocal的作用域和多线程环境,以及控制器和服务可能不在同一个线程中。

解决方案

1,不优雅的方法

在创建线程的之前把ThreadLocal中内容取出,然后设置到子线程的ThreadLocal中。

但这样做会侵入业务代码,且每使用一个线程就要写这样一段代码,还会导致大量冗余代码。

2,优雅的方法

创建一个抽象类,实现Runnable方法。

cpp 复制代码
public abstract class MyRunnable implements Runnable{
    private RequestAttributes requestAttributes;

    public MyRunnable() {
        requestAttributes = RequestContextHolder.getRequestAttributes();
    }

    public abstract void myRun();

    @Override
    public void run() {
        //每一个线程都来共享之前的请求数据
        RequestContextHolder.setRequestAttributes(requestAttributes);
        myRun();
    }
}

用法如下:

使用第二种方法,巧妙地把设置对象的工作放在了创建线程对象的构造过程中。

cpp 复制代码
//开启第一个异步任务
        CompletableFuture<Void> addressFuture2 = CompletableFuture.runAsync(new MyRunnable() {
            @Override
            public void myRun() {
                //1、远程查询所有的收获地址列表
                List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
                confirmVo.setMemberAddressVos(address);
            }
        }, threadPoolExecutor);
相关推荐
lhldsg6 小时前
全民健身解决方案小程序开发:从0到1的技术实战
java·数据库·需求分析
jason成都7 小时前
Spring WebFlux 适配达梦新方案|dm‑r2dbc:Netty 异步传输的实验性 R2DBC 驱动
java·后端·spring
泡海椒7 小时前
内置SPI函数库详解:JQuick-Java Builtin工具类实战用法
java·开发语言·python
一米阳光86617 小时前
软考高级【信息系统项目管理师】高项重要考点(3)高级项目管理-下 助你顺利上岸!
笔记·职场发展·软考·信息系统项目管理师·高级职称
辰烨chenye7 小时前
LeetCode Hot 100 题解 · 二分篇
java·算法·leetcode
dianziyao_7 小时前
第 4.1 篇:让 Codex 理解你的双链——AI 辅助发现笔记间的关联
人工智能·笔记
小羊没烦恼!7 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
小荷才露尖尖角,早有蜻蜓立上头9 小时前
过滤器的4种创建方式
java
大圣编蚕9 小时前
Java ByteArrayInputStream 详解:从入门到实战
java·开发语言·算法
程序员阿明9 小时前
idea把插件啥的不默认放在C盘
java·ide·intellij-idea