SpringBoot中使用異步線程導(dǎo)致Request請求頭丟失問題的解決方法
背景
異步線程請求頭丟失的問題通常發(fā)生在多線程環(huán)境中,特別是在使用 CompletableFuture 或其他異步編程模型時。具體原因如下:
異步線程請求頭丟失的原因
當(dāng)主線程將任務(wù)提交到異步線程池執(zhí)行時,當(dāng)前線程中的 RequestAttributes(包括請求頭等上下文信息)不會自動傳遞到異步線程中。這是因為 RequestAttributes 是基于當(dāng)前線程的本地存儲(ThreadLocal)實現(xiàn)的,而異步線程是在不同的線程中執(zhí)行任務(wù),因此無法訪問到主線程中的 RequestAttributes。這種情況下,異步線程執(zhí)行任務(wù)時會丟失請求頭等上下文信息。
為了確保異步線程能夠正確獲取請求上下文信息,需要在任務(wù)執(zhí)行前后顯式地設(shè)置和重置 RequestAttributes。
一、問題描述
筆者在開發(fā)中使用CompletableFuture去異步調(diào)用openFegin服務(wù),但是在傳遞過程中發(fā)現(xiàn)了問題,原來存儲在header中的信息,在進(jìn)入其他服務(wù)后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();
這種寫法就導(dǎo)致了請求頭信息丟失,服務(wù)調(diào)用的發(fā)起方在system服務(wù)模塊,header中的租戶id為12361

請求在到達(dá)business服務(wù)模塊或者inspection服務(wù)模塊時,租戶id丟失,變成了默認(rèn)值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();
接著在異步線程中重新設(shè)置下異步線程所攜帶請求頭的信息,這樣就保證了請求頭的連續(xù)傳遞性
三、拓展
不想每次都在CompletableFuture中都寫如下設(shè)置請求頭信息的代碼怎么辦?
RequestContextHolder.setRequestAttributes(requestAttributes);
筆者這里給出一個自認(rèn)為相對能減少點代碼量的解決方法(具體有無必要完全看個人)
自定義一個繼承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
* 異步線程傳遞請求頭,跨服務(wù)調(diào)用時需要傳遞請求頭信息
*/
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;
}
}
使用方法如下,筆者設(shè)置了一個可以傳遞線程池的參數(shù)
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();
這種寫法就是減少了設(shè)置的代碼,傳一個參數(shù)進(jìn)去即可
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
這個可以寫在某些全局工具類如ServletUtils

使用時如下獲取即可
RequestAttributes requestAttributes = ServletUtils.getRequestAttributes();
四、總結(jié)
本篇文章主要描述了筆者實際開發(fā)中遇到的采用異步線程導(dǎo)致請求頭丟失的問題,本文詳細(xì)介紹了問題發(fā)生的場景和解決方法。實際可以從以下幾個方面考慮
- 顯式傳遞 RequestAttributes:
- 在異步任務(wù)執(zhí)行前,獲取當(dāng)前線程的 RequestAttributes。
- 在異步任務(wù)執(zhí)行時,顯式地設(shè)置 RequestAttributes。
- 在異步任務(wù)執(zhí)行后,重置 RequestAttributes,以避免影響后續(xù)任務(wù)。
- 使用自定義 CompletableFuture 實現(xiàn):
- 創(chuàng)建一個自定義的 CompletableFuture 實現(xiàn),在任務(wù)執(zhí)行前后自動設(shè)置和重置 RequestAttributes。
- 這樣可以確保每個異步任務(wù)都能訪問到正確的請求上下文信息。
- 使用 ThreadLocal 傳遞上下文信息:
- 利用 ThreadLocal 將請求上下文信息(如請求頭等)封裝在一個對象中。
- 在異步任務(wù)執(zhí)行前后,顯式地傳遞并設(shè)置這個對象。
- 使用 AOP(面向切面編程):
- 通過 AOP 在方法調(diào)用前后自動設(shè)置和重置 RequestAttributes。
- 這樣可以減少手動設(shè)置和重置 RequestAttributes 的代碼量。
- 使用 Spring 提供的工具類:
- 利用 Spring 提供的工具類(如 RequestContextHolder 和 RequestAttributes)來管理請求上下文。
- 在異步任務(wù)執(zhí)行前后,顯式地設(shè)置和重置這些工具類的狀態(tài)。
通過上述方法,可以確保異步線程在執(zhí)行任務(wù)時能夠正確訪問到請求上下文信息,從而避免請求頭丟失的問題。對您有幫助的話,請關(guān)注點贊支持一波哦!
以上就是SpringBoot中使用異步線程導(dǎo)致Request請求頭丟失問題的解決方法的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Request請求頭丟失的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java Optional解決空指針異??偨Y(jié)(java 8 功能)
這篇文章主要介紹了Java Optional解決空指針異常總結(jié)(java 8 功能),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
SpringBoot--- SpringSecurity進(jìn)行注銷權(quán)限控制的配置方法
這篇文章主要介紹了SpringBoot--- SpringSecurity進(jìn)行注銷,權(quán)限控制,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
Java String源碼分析并介紹Sting 為什么不可變
這篇文章主要介紹了Java String源碼分析并介紹Sting 為什么不可變的相關(guān)資料,需要的朋友可以參考下2017-02-02
Java 調(diào)用Restful API接口的幾種方式(HTTPS)
這篇文章主要介紹了Java 調(diào)用Restful API接口的幾種方式(HTTPS),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02

