SpringCloud Alibaba微服務(wù)實(shí)戰(zhàn)之遠(yuǎn)程Feign請(qǐng)求頭丟失問(wèn)題解決方案
導(dǎo)讀:在上一篇文章中我們講解了如何利用Spring Security OAuth2實(shí)現(xiàn)微服務(wù)統(tǒng)一認(rèn)證,今天繼續(xù)講解Feign遠(yuǎn)程調(diào)用和異步調(diào)用請(qǐng)求頭丟失問(wèn)題。
簡(jiǎn)介
之前演示只是測(cè)試了調(diào)用用戶微服務(wù)下查詢接口,并沒有在用戶服務(wù)再調(diào)用其他微服務(wù)接口,調(diào)測(cè)看著一切都很正常,但今天測(cè)試了用戶微服務(wù)調(diào)用商品微服務(wù),出現(xiàn)了異常:

Debug跟蹤,發(fā)現(xiàn)用戶微服務(wù)正常接收到token,而遠(yuǎn)程調(diào)用商品微服務(wù)token消失不見了:
原因

源碼:ReflectiveFeign.class:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"equals".equals(method.getName())) {
if ("hashCode".equals(method.getName())) {
return this.hashCode();
} else {
return "toString".equals(method.getName()) ? this.toString() : ((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(args);
}
} else {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}進(jìn)入((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(args):
public Object invoke(Object[] argv) throws Throwable {
//就是在這 構(gòu)建了一個(gè)新的RequestTemplate ,而瀏覽器帶給我們的請(qǐng)求頭都會(huì)丟失
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Request.Options options = this.findOptions(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
// 在這即將執(zhí)行該方法
return this.executeAndDecode(template, options);
} catch (RetryableException var9) {
RetryableException e = var9;
try {
retryer.continueOrPropagate(e);
} catch (RetryableException var8) {
Throwable cause = var8.getCause();
if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
throw cause;
}
throw var8;
}
if (this.logLevel != Level.NONE) {
this.logger.logRetry(this.metadata.configKey(), this.logLevel);
}
}
}
}進(jìn)入this.executeAndDecode(template, options):
Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {
//這里 它會(huì)對(duì)我們的請(qǐng)求進(jìn)行一些包裝
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
}
long start = System.nanoTime();
Response response;
try {
response = this.client.execute(request, options);
response = response.toBuilder().request(request).requestTemplate(template).build();
} catch (IOException var12) {
if (this.logLevel != Level.NONE) {
this.logger.logIOException(this.metadata.configKey(), this.logLevel, var12, this.elapsedTime(start));
}
throw FeignException.errorExecuting(request, var12);
}
.......
}進(jìn)入this.targetRequest(template):
Request targetRequest(RequestTemplate template) {
//拿到對(duì)應(yīng)的所有請(qǐng)求攔截器的迭代器
Iterator var2 = this.requestInterceptors.iterator();
//遍歷所有的請(qǐng)求攔截器
while(var2.hasNext()) {
RequestInterceptor interceptor = (RequestInterceptor)var2.next();
//這里是每個(gè)請(qǐng)求攔截器 依次對(duì)該方法進(jìn)行包裝
interceptor.apply(template);
}
return this.target.apply(template);
}進(jìn)入interceptor.apply(template):
public interface RequestInterceptor {
void apply(RequestTemplate var1);
}發(fā)現(xiàn)它是一個(gè)接口,所以可以重寫一下這個(gè)方法對(duì)我們的請(qǐng)求做一些包裝,借鑒一下別的實(shí)現(xiàn)方法:

方案
/**
* 微服務(wù)之間feign調(diào)用請(qǐng)求頭丟失的問(wèn)題
* @author yian
* @since 2023-04-05
*/
@Configuration
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest httpServletRequest = getHttpServletRequest();
if(httpServletRequest!=null){
Map<String, String> headers = getHeaders(httpServletRequest);
// 傳遞所有請(qǐng)求頭,防止部分丟失
//此處也可以只傳遞認(rèn)證的header
//requestTemplate.header("json-token", request.getHeader("json-token"));
for (Map.Entry<String, String> entry : headers.entrySet()) {
template.header(entry.getKey(), entry.getValue());
}
log.debug("FeignRequestInterceptor:{}", template.toString());
}
}
private HttpServletRequest getHttpServletRequest() {
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (Exception e) {
return null;
}
}
/**
* 獲取原請(qǐng)求頭
*/
private Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
if(enumeration!=null){
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
}
return map;
}
}
補(bǔ)充說(shuō)明
實(shí)際開發(fā)中,在業(yè)務(wù)復(fù)雜情況下難免使用異步編排的方式實(shí)現(xiàn),這個(gè)時(shí)候你會(huì)發(fā)現(xiàn)請(qǐng)求頭又丟失了。

源碼:RequestContextHolder.class:
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
if (attributes == null) {
attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
}
return attributes;
}查看requestAttributesHolder變量:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");查看NamedThreadLocal:
public class NamedThreadLocal<T> extends ThreadLocal<T> {
private final String name;
public NamedThreadLocal(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}
public String toString() {
return this.name;
}
}
ThreadLocal是一個(gè)線程局部變量,在不同線程之間是獨(dú)立的所以我們獲取不到原先主線程的請(qǐng)求屬性。
方案
//獲取之前的請(qǐng)求
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> getAddress = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getId());
//每一個(gè)線程都來(lái)共享之前請(qǐng)求的數(shù)據(jù)
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
confirmVo.setAddress(address);
}, executor);至此我們已經(jīng)解決了Feign遠(yuǎn)程以及異步編排下導(dǎo)致的請(qǐng)求頭丟失問(wèn)題。

到此這篇關(guān)于SpringCloud Alibaba微服務(wù)實(shí)戰(zhàn)之遠(yuǎn)程Feign請(qǐng)求頭丟失的文章就介紹到這了,更多相關(guān)SpringCloud Alibaba Feign請(qǐng)求頭內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 網(wǎng)絡(luò)編程之TCP通信和簡(jiǎn)單的文件上傳功能實(shí)例
下面小編就為大家分享一篇java 網(wǎng)絡(luò)編程之TCP通信和簡(jiǎn)單的文件上傳功能實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Spring boot集成redis lettuce代碼實(shí)例
這篇文章主要介紹了Spring boot集成redis lettuce代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
SpringBoot集成Kafka 配置工具類的詳細(xì)代碼
spring-kafka 是基于 java版的 kafka client與spring的集成,提供了 KafkaTemplate,封裝了各種方法,方便操作,它封裝了apache的kafka-client,不需要再導(dǎo)入client依賴,這篇文章主要介紹了SpringBoot集成Kafka 配置工具類,需要的朋友可以參考下2022-09-09
Java實(shí)現(xiàn)九九乘法表的完整實(shí)例(對(duì)齊版)
這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)九九乘法表(對(duì)齊版)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
JavaWeb中轉(zhuǎn)發(fā)與重定向的區(qū)別小結(jié)
轉(zhuǎn)發(fā)和重定向是JavaWeb中常用的兩種頁(yè)面跳轉(zhuǎn)方式,它們?cè)趯?shí)現(xiàn)上有一些區(qū)別,本文主要介紹了JavaWeb中轉(zhuǎn)發(fā)與重定向的區(qū)別小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10
Java Redis Template批量查詢指定鍵值對(duì)的實(shí)現(xiàn)
本文主要介紹了Java Redis Template批量查詢指定鍵值對(duì)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Log4j定時(shí)打印日志及添加模塊名配置的Java代碼實(shí)例
這篇文章主要介紹了Log4j定時(shí)打印日志及添加模塊名配置的Java代碼實(shí)例,Log4j是Apache的一個(gè)開源Java日志項(xiàng)目,需要的朋友可以參考下2016-01-01
Maven3種打包方式中maven-assembly-plugin的使用詳解
這篇文章主要介紹了Maven3種打包方式中maven-assembly-plugin的使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07

