SpringBoot中使用异步线程导致Request请求头丢失问题的解决方法

 更新时间:2025年07月23日 10:22:41   作者:就胡编乱码  
异步线程请求头丢失的问题通常发生在多线程环境中,特别是在使用 CompletableFuture 或其他异步编程模型时,本文给大家详细介绍了SpringBoot中使用异步线程导致Request请求头丢失问题的原因和解决方法,需要的朋友可以参考下

背景

异步线程请求头丢失的问题通常发生在多线程环境中,特别是在使用 CompletableFuture 或其他异步编程模型时。具体原因如下:

异步线程请求头丢失的原因

当主线程将任务提交到异步线程池执行时,当前线程中的 RequestAttributes(包括请求头等上下文信息)不会自动传递到异步线程中。这是因为 RequestAttributes 是基于当前线程的本地存储(ThreadLocal)实现的,而异步线程是在不同的线程中执行任务,因此无法访问到主线程中的 RequestAttributes。这种情况下,异步线程执行任务时会丢失请求头等上下文信息。
为了确保异步线程能够正确获取请求上下文信息,需要在任务执行前后显式地设置和重置 RequestAttributes。

一、问题描述

笔者在开发中使用CompletableFuture去异步调用openFegin服务,但是在传递过程中发现了问题,原来存储在header中的信息,在进入其他服务后header中存储的user信息都丢失了

CompletableFuture<R<Boolean>> orderCheckFuture = CompletableFuture.supplyAsync(() -> remoteBusinessService.checkUnfinishedOrder(SecurityConstants.INNER, userId));
CompletableFuture<R<Boolean>> taskCheckFuture = CompletableFuture.supplyAsync(() -> remoteInspectionService.checkUnfinishedTask(SecurityConstants.INNER, userId));
CompletableFuture.allOf(orderCheckFuture, taskCheckFuture).join();
R<Boolean> orderCheck = orderCheckFuture.join();
R<Boolean> taskCheck = taskCheckFuture.join();

这种写法就导致了请求头信息丢失,服务调用的发起方在system服务模块,header中的租户id为12361

请求在到达business服务模块或者inspection服务模块时,租户id丢失,变成了默认值9999

二、解决方案

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

CompletableFuture<R<Boolean>> orderCheckFuture = CompletableFuture.supplyAsync(() -> {
    RequestContextHolder.setRequestAttributes(requestAttributes);
    return remoteBusinessService.checkUnfinishedOrder(SecurityConstants.INNER, userId);
});

CompletableFuture<R<Boolean>> taskCheckFuture = CompletableFuture.supplyAsync(() -> {
    RequestContextHolder.setRequestAttributes(requestAttributes);
    return remoteInspectionService.checkUnfinishedTask(SecurityConstants.INNER, userId);
});

CompletableFuture.allOf(orderCheckFuture, taskCheckFuture).join();

R<Boolean> orderCheck = orderCheckFuture.join();
R<Boolean> taskCheck = taskCheckFuture.join();

先通过如下代码获取请求头属性

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

接着在异步线程中重新设置下异步线程所携带请求头的信息,这样就保证了请求头的连续传递性

三、拓展

不想每次都在CompletableFuture中都写如下设置请求头信息的代码怎么办?

RequestContextHolder.setRequestAttributes(requestAttributes);

笔者这里给出一个自认为相对能减少点代码量的解决方法(具体有无必要完全看个人)

自定义一个继承CompletableFuture的类 CustomCompletableFuture

package com.zlbc.common.core.async;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.RequestAttributes;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Supplier;

/**
 * @author hulei
 * 异步线程传递请求头,跨服务调用时需要传递请求头信息
 */
public class CustomCompletableFuture<T> extends CompletableFuture<T> {

    public static <T> CustomCompletableFuture<T> supplyAsync(Supplier<T> supplier, Executor executor, RequestAttributes requestAttributes) {
        CustomCompletableFuture<T> future = new CustomCompletableFuture<>();
        executor.execute(() -> {
            try {
                RequestContextHolder.setRequestAttributes(requestAttributes);
                T result = supplier.get();
                future.complete(result);
            } catch (Exception e) {
                future.completeExceptionally(e);
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        });
        return future;
    }

    public static CustomCompletableFuture<Void> runAsync(Runnable runnable, Executor executor, RequestAttributes requestAttributes) {
        CustomCompletableFuture<Void> future = new CustomCompletableFuture<>();
        executor.execute(() -> {
            try {
                RequestContextHolder.setRequestAttributes(requestAttributes);
                runnable.run();
                future.complete(null);
            } catch (Exception e) {
                future.completeExceptionally(e);
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        });
        return future;
    }
}


使用方法如下,笔者设置了一个可以传递线程池的参数

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        
ExecutorService customExecutor = Executors.newFixedThreadPool(5);
        
CustomCompletableFuture<R<Boolean>> orderCheckFuture = CustomCompletableFuture.supplyAsync(
                () -> remoteBusinessService.checkUnfinishedOrder(SecurityConstants.INNER, userId),
                customExecutor,
                requestAttributes
);

CustomCompletableFuture<R<Boolean>> taskCheckFuture = CustomCompletableFuture.supplyAsync(
                () -> remoteInspectionService.checkUnfinishedTask(SecurityConstants.INNER, userId),
                customExecutor,
                requestAttributes
);

CompletableFuture.allOf(orderCheckFuture, taskCheckFuture).join();

R<Boolean> orderCheck = orderCheckFuture.join();
R<Boolean> taskCheck = taskCheckFuture.join();

这种写法就是减少了设置的代码,传一个参数进去即可

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

这个可以写在某些全局工具类如ServletUtils

使用时如下获取即可

RequestAttributes requestAttributes = ServletUtils.getRequestAttributes();

四、总结

本篇文章主要描述了笔者实际开发中遇到的采用异步线程导致请求头丢失的问题,本文详细介绍了问题发生的场景和解决方法。实际可以从以下几个方面考虑

  1. 显式传递 RequestAttributes:
  • 在异步任务执行前,获取当前线程的 RequestAttributes。
  • 在异步任务执行时,显式地设置 RequestAttributes。
  • 在异步任务执行后,重置 RequestAttributes,以避免影响后续任务。
  1. 使用自定义 CompletableFuture 实现:
  • 创建一个自定义的 CompletableFuture 实现,在任务执行前后自动设置和重置 RequestAttributes。
  • 这样可以确保每个异步任务都能访问到正确的请求上下文信息。
  1. 使用 ThreadLocal 传递上下文信息:
  • 利用 ThreadLocal 将请求上下文信息(如请求头等)封装在一个对象中。
  • 在异步任务执行前后,显式地传递并设置这个对象。
  1. 使用 AOP(面向切面编程):
  • 通过 AOP 在方法调用前后自动设置和重置 RequestAttributes。
  • 这样可以减少手动设置和重置 RequestAttributes 的代码量。
  1. 使用 Spring 提供的工具类:
  • 利用 Spring 提供的工具类(如 RequestContextHolder 和 RequestAttributes)来管理请求上下文。
  • 在异步任务执行前后,显式地设置和重置这些工具类的状态。

通过上述方法,可以确保异步线程在执行任务时能够正确访问到请求上下文信息,从而避免请求头丢失的问题。对您有帮助的话,请关注点赞支持一波哦!

以上就是SpringBoot中使用异步线程导致Request请求头丢失问题的解决方法的详细内容,更多关于SpringBoot Request请求头丢失的资料请关注脚本之家其它相关文章!

相关文章

  • 详解JAVA设计模式之模板模式

    详解JAVA设计模式之模板模式

    这篇文章主要介绍了详解JAVA设计模式之模板模式的的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • java必学必会之网络编程

    java必学必会之网络编程

    java必学必会之网络编程,学习了解java网络编程、网络通信协议、TCP协议和UDP协议,对各个协议进行深入学习,做到必学必会
    2015-12-12
  • java 生成二维码实例

    java 生成二维码实例

    这篇文章主要介绍了java 生成二维码的实例,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • 浅谈springboot @Repository与@Mapper的区别

    浅谈springboot @Repository与@Mapper的区别

    本文主要介绍了浅谈springboot @Repository与@Mapper的区别,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • spring retry方法调用失败重试机制示例解析

    spring retry方法调用失败重试机制示例解析

    这篇文章主要为大家介绍了spring retry方法调用失败重试机制的示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03
  • Java中的内存区域(堆、栈、方法区等)分别存储什么详解

    Java中的内存区域(堆、栈、方法区等)分别存储什么详解

    Java把内存分成两种,一种叫做栈内存,一种叫做堆内存,下面这篇文章主要介绍了Java中的内存区域(堆、栈、方法区等)分别存储什么的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-07-07
  • Java游戏开发之俄罗斯方块的实现

    Java游戏开发之俄罗斯方块的实现

    俄罗斯方块是一个最初由阿列克谢帕吉特诺夫在苏联设计和编程的益智类视频游戏。本文和大家分享了利用Java语言实现这一经典的小游戏的示例代码,需要的可以参考一下
    2022-05-05
  • Junit springboot打印测试方法信息

    Junit springboot打印测试方法信息

    这篇文章主要介绍了Junit springboot打印测试方法信息,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Java中基于Shiro,JWT实现微信小程序登录完整例子及实现过程

    Java中基于Shiro,JWT实现微信小程序登录完整例子及实现过程

    这篇文章主要介绍了Java中基于Shiro,JWT实现微信小程序登录完整例子 ,实现了小程序的自定义登陆,将自定义登陆态token返回给小程序作为登陆凭证。需要的朋友可以参考下
    2018-11-11
  • 解决Maven项目报错:failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0的问题

    解决Maven项目报错:failed to execute goal org.apache.maven.plug

    这篇文章主要介绍了解决Maven项目报错:failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05

最新评论