SpringCloud Feign轉(zhuǎn)發(fā)請(qǐng)求頭(防止session失效)的解決方案
微服務(wù)開(kāi)發(fā)中經(jīng)常有這樣的需求,公司自定義了通用的請(qǐng)求頭,需要在微服務(wù)的調(diào)用鏈中轉(zhuǎn)發(fā),比如在請(qǐng)求頭中加入了token,或者某個(gè)自定義的信息uniqueId,總之就是自定義的一個(gè)鍵值對(duì)的東東,A服務(wù)調(diào)用B服務(wù),B服務(wù)調(diào)用C服務(wù),這樣通用的東西如何讓他在一個(gè)調(diào)用鏈中不斷地傳遞下去呢?以A服務(wù)為例:
方案1
最傻的辦法,在程序中獲取,調(diào)用B的時(shí)候再轉(zhuǎn)發(fā),怎么獲取在Controller中國(guó)通過(guò)注解獲取,或者通過(guò)request對(duì)象獲取,這個(gè)不難,在請(qǐng)求B服務(wù)的時(shí)候,通過(guò)注解將值放進(jìn)去即可;簡(jiǎn)代碼如下:
獲?。? @RequestMapping(value = "/api/test", method = RequestMethod.GET) public String testFun(@RequestParam String name, @RequestHeader("uniqueId") String uniqueId) { if(uniqueId == null ){ return "Must defined the uniqueId , it can not be null"; } log.info(uniqueId, "begin testFun... "); return uniqueId; }
然后A使用Feign調(diào)用B服務(wù)的時(shí)候,傳過(guò)去:
@FeignClient(value = "DEMO-SERVICE") public interface CallClient { /** * 訪問(wèn)DEMO-SERVICE服務(wù)的/api/test接口,通過(guò)注解將logId傳遞給下游服務(wù) */ @RequestMapping(value = "/api/test", method = RequestMethod.GET) String callApiTest(@RequestParam(value = "name") String name, @RequestHeader(value = "uniqueId") String uniqueId); }
方案弊端:毫無(wú)疑問(wèn),這方案不好,因?yàn)閷?duì)代碼有侵入,需要開(kāi)發(fā)人員沒(méi)次手動(dòng)的獲取和添加,因此舍棄
方案2
服務(wù)通過(guò)請(qǐng)求攔截器,在請(qǐng)求從A發(fā)送到B之后,在攔截器內(nèi)將自己需要的東東加到請(qǐng)求頭:
import com.intellif.log.LoggerUtilI; import feign.RequestInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; /** * 自定義的請(qǐng)求頭處理類(lèi),處理服務(wù)發(fā)送時(shí)的請(qǐng)求頭; * 將服務(wù)接收到的請(qǐng)求頭中的uniqueId和token字段取出來(lái),并設(shè)置到新的請(qǐng)求頭里面去轉(zhuǎn)發(fā)給下游服務(wù) * 比如A服務(wù)收到一個(gè)請(qǐng)求,請(qǐng)求頭里面包含uniqueId和token字段,A處理時(shí)會(huì)使用Feign客戶端調(diào)用B服務(wù) * 那么uniqueId和token這兩個(gè)字段就會(huì)添加到請(qǐng)求頭中一并發(fā)給B服務(wù); * * @author mozping * @version 1.0 * @date 2018/6/27 14:13 * @see FeignHeadConfiguration * @since JDK1.8 */ @Configuration public class FeignHeadConfiguration { private final LoggerUtilI logger = LoggerUtilI.getLogger(this.getClass().getName()); @Bean public RequestInterceptor requestInterceptor() { return requestTemplate -> { ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attrs != null) { HttpServletRequest request = attrs.getRequest(); Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String value = request.getHeader(name); /** * 遍歷請(qǐng)求頭里面的屬性字段,將logId和token添加到新的請(qǐng)求頭中轉(zhuǎn)發(fā)到下游服務(wù) * */ if ("uniqueId".equalsIgnoreCase(name) || "token".equalsIgnoreCase(name)) { logger.debug("添加自定義請(qǐng)求頭key:" + name + ",value:" + value); requestTemplate.header(name, value); } else { logger.debug("FeignHeadConfiguration", "非自定義請(qǐng)求頭key:" + name + ",value:" + value + "不需要添加!"); } } } else { logger.warn("FeignHeadConfiguration", "獲取請(qǐng)求頭失?。?); } } }; } }
網(wǎng)上很多關(guān)于這種方法的博文或者資料,大同小異,但是有一個(gè)問(wèn)題,在開(kāi)啟熔斷器之后,這里的attrs就是null,因?yàn)槿蹟嗥髂J(rèn)的隔離策略是thread,也就是線程隔離,實(shí)際上接收到的對(duì)象和這個(gè)在發(fā)送給B不是一個(gè)線程,怎么辦?有一個(gè)辦法,修改隔離策略hystrix.command.default.execution.isolation.strategy=SEMAPHORE,改為信號(hào)量的隔離模式,但是不推薦,因?yàn)閠hread是默認(rèn)的,而且要命的是信號(hào)量模式,熔斷器不生效,比如設(shè)置了熔斷時(shí)間hystrix.command.default.execution.isolation.semaphore.timeoutInMilliseconds=5000,五秒,如果B服務(wù)里面sleep了10秒,非得等到B執(zhí)行完畢再返回,因此這個(gè)方案也不可?。坏怯惺裁崔k法可以在默認(rèn)的Thread模式下讓攔截器拿到上游服務(wù)的請(qǐng)求頭?自定義策略:代碼如下:
import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable; import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; import com.netflix.hystrix.strategy.properties.HystrixProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 自定義Feign的隔離策略; * 在轉(zhuǎn)發(fā)Feign的請(qǐng)求頭的時(shí)候,如果開(kāi)啟了Hystrix,Hystrix的默認(rèn)隔離策略是Thread(線程隔離策略),因此轉(zhuǎn)發(fā)攔截器內(nèi)是無(wú)法獲取到請(qǐng)求的請(qǐng)求頭信息的,可以修改默認(rèn)隔離策略為信號(hào)量模式:hystrix.command.default.execution.isolation.strategy=SEMAPHORE,這樣的話轉(zhuǎn)發(fā)線程和請(qǐng)求線程實(shí)際上是一個(gè)線程,這并不是最好的解決方法,信號(hào)量模式也不是官方最為推薦的隔離策略;另一個(gè)解決方法就是自定義Hystrix的隔離策略,思路是將現(xiàn)有的并發(fā)策略作為新并發(fā)策略的成員變量,在新并發(fā)策略中,返回現(xiàn)有并發(fā)策略的線程池、Queue;將策略加到Spring容器即可; * * @author mozping * @version 1.0 * @date 2018/7/5 9:08 * @see FeignHystrixConcurrencyStrategyIntellif * @since JDK1.8 */ @Component public class FeignHystrixConcurrencyStrategyIntellif extends HystrixConcurrencyStrategy { private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategyIntellif.class); private HystrixConcurrencyStrategy delegate; public FeignHystrixConcurrencyStrategyIntellif() { try { this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); if (this.delegate instanceof FeignHystrixConcurrencyStrategyIntellif) { // Welcome to singleton hell... return; } HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook(); HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier(); HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher(); HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy); HystrixPlugins.reset(); HystrixPlugins.getInstance().registerConcurrencyStrategy(this); HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook); HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); } catch (Exception e) { log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e); } } private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier, HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) { if (log.isDebugEnabled()) { log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy [" + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher [" + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]"); log.debug("Registering Sleuth Hystrix Concurrency Strategy."); } } @Override public <T> Callable<T> wrapCallable(Callable<T> callable) { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); return new WrappedCallable<>(callable, requestAttributes); } @Override public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties); } @Override public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) { return this.delegate.getBlockingQueue(maxQueueSize); } @Override public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) { return this.delegate.getRequestVariable(rv); } static class WrappedCallable<T> implements Callable<T> { private final Callable<T> target; private final RequestAttributes requestAttributes; public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) { this.target = target; this.requestAttributes = requestAttributes; } @Override public T call() throws Exception { try { RequestContextHolder.setRequestAttributes(requestAttributes); return target.call(); } finally { RequestContextHolder.resetRequestAttributes(); } } } }
然后使用默認(rèn)的熔斷器隔離策略,也可以在攔截器內(nèi)獲取到上游服務(wù)的請(qǐng)求頭信息了;
這里參考的博客,感謝這位大牛:https://blog.csdn.net/Crystalqy/article/details/79083857
到此這篇關(guān)于SpringCloud Feign轉(zhuǎn)發(fā)請(qǐng)求頭(防止session失效)的解決方案的文章就介紹到這了,更多相關(guān)SpringCloud Feign轉(zhuǎn)發(fā)請(qǐng)求頭內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java阻塞隊(duì)列(BlockingQueue)的實(shí)現(xiàn)原理
這篇文章主要介紹了詳解Java阻塞隊(duì)列(BlockingQueue)的實(shí)現(xiàn)原理,阻塞隊(duì)列是Java util.concurrent包下重要的數(shù)據(jù)結(jié)構(gòu),有興趣的可以了解一下2017-06-06面試JAVA時(shí),問(wèn)到spring該怎么回答
這篇文章主要介紹了Spring面試資料,學(xué)Java的小伙伴都知道Spring是面試的必問(wèn)環(huán)節(jié),看完了一天就可掌握數(shù)據(jù)結(jié)構(gòu)和算法的面試題,快來(lái)看看吧2021-08-08一起來(lái)學(xué)習(xí)Java IO的轉(zhuǎn)化流
這篇文章主要為大家詳細(xì)介紹了Java IO的轉(zhuǎn)化流,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03springboot使用EMQX(MQTT協(xié)議)的實(shí)現(xiàn)
最近由于iot越來(lái)越火, 物聯(lián)網(wǎng)的需求越來(lái)越多, 那么理所當(dāng)然的使用mqtt的場(chǎng)景也就越來(lái)越多,本文主要介紹了springboot使用EMQX(MQTT協(xié)議)的實(shí)現(xiàn),感興趣的可以了解一下2023-10-10Java陷阱之a(chǎn)ssert關(guān)鍵字詳解
這篇文章詳細(xì)介紹了Java陷阱之a(chǎn)ssert關(guān)鍵字,有需要的朋友可以參考一下2013-09-09使用開(kāi)源項(xiàng)目JAVAE2 進(jìn)行視頻格式轉(zhuǎn)換
這篇文章主要介紹了使用開(kāi)源項(xiàng)目JAVAE 進(jìn)行視頻格式轉(zhuǎn)換,幫助大家更好的利用Java處理視頻,完成自身需求,感興趣的朋友可以了解下2020-11-11實(shí)體類(lèi)使用@Builder,導(dǎo)致@ConfigurationProperties注入屬性失敗問(wèn)題
這篇文章主要介紹了實(shí)體類(lèi)使用@Builder,導(dǎo)致@ConfigurationProperties注入屬性失敗問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12MyBatis實(shí)現(xiàn)MySQL批量插入的示例代碼
本文主要介紹了MyBatis實(shí)現(xiàn)MySQL批量插入的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05Springmvc異常映射2種實(shí)現(xiàn)方法
這篇文章主要介紹了Springmvc異常映射2種實(shí)現(xiàn)方法以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。,需要的朋友可以參考下2020-05-05