使用Feign動(dòng)態(tài)設(shè)置header和原理分析
Feign動(dòng)態(tài)設(shè)置header和原理
項(xiàng)目中用到了Feign做遠(yuǎn)程調(diào)用, 有部分場景需要?jiǎng)討B(tài)配置header
開始的做法是通過 @RequestHeader 設(shè)置參數(shù)來實(shí)現(xiàn)動(dòng)態(tài)的header配置
例如
@GetMapping(value = "/test", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE}) ? ? String access(@RequestHeader("Auth") String auth, @RequestBody Expression expression);
這種方式雖然可以達(dá)到header的動(dòng)態(tài)配置, 但是當(dāng)參數(shù)過多時(shí)會降低接口可用性, 所以想通過傳遞bean的方式來設(shè)置header
先說解決辦法
public class HeaderInterceptor implements RequestInterceptor { ? ? @Override ? ? public void apply(RequestTemplate requestTemplate) { ? ? ? ? byte[] bytes = requestTemplate.requestBody().asBytes(); ? ? ? ? Identity identity = JSONObject.parseObject(bytes, Identity.class); ? ? ? ? requestTemplate.header("Auth", identity.getSecret()); ? ? } }? /** ?* configuration指定Interceptor **/ @FeignClient(name = "test", url = "127.0.0.1:8300", configuration = HeaderInterceptor.class) public interface GolangTestHandle2 { ? ? @GetMapping(value = "/handler", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE}) ? ? String handle(Identity identity); }
自定義Interceptor實(shí)現(xiàn)RequestInterceptor接口, 回調(diào)方法apply提供了RequestTemplate對象, 對象內(nèi)部封裝了request的所有信息, 最后通過configuration指定接口, 之后就隨便你怎么玩了(例如通過body獲取接口參數(shù)并動(dòng)態(tài)設(shè)置header)
值得注意的一點(diǎn)是HeaderInterceptor如果注入到Springboot容器的話會全局生效, 就是說及時(shí)沒有指定configuration也會對全局feign接口生效, 為什么呢? 這里簡單說明一下
首先Feign為每個(gè)feign class創(chuàng)建springcontext上下文
spring通過調(diào)用getObject獲取feign工廠實(shí)例
? ? @Override ? ? public Object getObject() throws Exception { ? ? ? ? return getTarget(); ? ? }
內(nèi)部調(diào)用FeignClientFatoryBean.getTarget()方法
<T> T getTarget() { ? ? ? ? //獲取feign上下文 ? ? ? ? FeignContext context = this.applicationContext.getBean(FeignContext.class); ? ? ? ? //構(gòu)建feign Builder ? ? ? ? Feign.Builder builder = feign(context); ? ? ? ? ... ? ? }
根據(jù)feign(FeignContext context)構(gòu)建Builder
protected Feign.Builder feign(FeignContext context) { ? ? ? ? FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); ? ? ? ? Logger logger = loggerFactory.create(this.type); ? ? ? ? // @formatter:off ? ? ? ? Feign.Builder builder = get(context, Feign.Builder.class) ? ? ? ? ? ? ? ? // required values ? ? ? ? ? ? ? ? .logger(logger) ? ? ? ? ? ? ? ? //默認(rèn)springEncoder ? ? ? ? ? ? ? ? .encoder(get(context, Encoder.class)) ? ? ? ? ? ? ? ? //默認(rèn)OptionalDecoder ? ? ? ? ? ? ? ? .decoder(get(context, Decoder.class)) ? ? ? ? ? ? ? ? //默認(rèn)SpringMvcContrat ? ? ? ? ? ? ? ? .contract(get(context, Contract.class)); ? ? ? ? // @formatter:on ? ? ? ? //配置該feign的context ? ? ? ? configureFeign(context, builder); ? ? ? ? return builder; ? ? }
在構(gòu)建過程中通過FeignClientFactoryBean.configureUsingConfiguration為feign class注冊基本的配置項(xiàng), 其中也包括了Interceptor的注冊
? ? protected void configureUsingConfiguration(FeignContext context, ? ? ? ? ? ? Feign.Builder builder) { ? ? ? ? Logger.Level level = getOptional(context, Logger.Level.class); ? ? ? ? if (level != null) { ? ? ? ? ? ? builder.logLevel(level); ? ? ? ? } ? ? ? ? Retryer retryer = getOptional(context, Retryer.class); ? ? ? ? if (retryer != null) { ? ? ? ? ? ? builder.retryer(retryer); ? ? ? ? } ? ? ? ? ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class); ? ? ? ? if (errorDecoder != null) { ? ? ? ? ? ? builder.errorDecoder(errorDecoder); ? ? ? ? } ? ? ? ? Request.Options options = getOptional(context, Request.Options.class); ? ? ? ? if (options != null) { ? ? ? ? ? ? builder.options(options); ? ? ? ? } ? ? ? ? //從feign context獲取interceptors ? ? ? ? Map<String, RequestInterceptor> requestInterceptors = context ? ? ? ? ? ? ? ? .getInstances(this.contextId, RequestInterceptor.class); ? ? ? ? if (requestInterceptors != null) { ? ? ? ? ? ? builder.requestInterceptors(requestInterceptors.values()); ? ? ? ? } ? ? ? ? if (this.decode404) { ? ? ? ? ? ? builder.decode404(); ? ? ? ? } ? ? }
contextId為具體的feign class id, RequestInterceptor為具體的接口, 即是說通過context.getInstances獲取所有RequestInterceptor實(shí)例并注冊到builder中.
? ? public <T> Map<String, T> getInstances(String name, Class<T> type) { ? ? ? ? AnnotationConfigApplicationContext context = getContext(name); ? ? ? ? //使用beanNamesForTypeIncludingAncestors ? ? ? ? if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, ? ? ? ? ? ? ? ? type).length > 0) { ? ? ? ? ? ? return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type); ? ? ? ? } ? ? ? ? return null; ? ? }
獲取工廠中的實(shí)例使用的是beanNamesForTypeIncludingAncestors方法, 該方法不僅會從feign的factory中查找, 也會通過父級別spring工廠查找相應(yīng)實(shí)例(類似于springmvc的工廠)
也是因?yàn)樵摲椒? 即使你沒有在FeignClient中配置configuration, 但是你的Interceptor通過@Component等方法注入容器的話也會全局生效的, 所以如果指向讓你的Interceptor部分生效不讓它注入到Spring容器就好
設(shè)置Feign的header信息(兩種形式)
在使用微服務(wù)SpringCloud全家桶組件Fegin的時(shí)候,我們在進(jìn)行遠(yuǎn)程服務(wù)之間調(diào)用的同時(shí),為了防止客戶端劫持信息,我們需要將一些敏感信息添加到我們的Fegin頭部(Header)當(dāng)中,今天朋友問起,總結(jié)一下:那么工作中常見的方式有兩種
1.在方法參數(shù)前面添加@RequestHeader注解
@PostMapping(value = "/getPersonDetail")? public ServerResponse getPersonDetail(@RequestBody Map map,@RequestHeader(name = "id") String id);
使用@RequestHeader(name = "id")可以傳遞動(dòng)態(tài)header屬性
2.實(shí)現(xiàn)RequestInterceptor接口
設(shè)置Header(所有的Fegin請求)
import org.springframework.context.annotation.Configuration;? import org.springframework.web.context.request.RequestContextHolder;? import org.springframework.web.context.request.ServletRequestAttributes;? import feign.RequestInterceptor;? import feign.RequestTemplate;? @Configuration? public class FeignConfiguration implements RequestInterceptor { ? ? ? ? ? ? @Override ? ? ? ? ? ? public void apply(RequestTemplate template) { ? ? ? ? ? ? ? ? ? ? ? ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); ? ? ?? ? ? ? ? ? ? ? ? HttpServletRequest request = attributes.getRequest(); ? ? ? ? ? ? ? ? ? ? ? ? Enumeration<String> headerNames = request.getHeaderNames(); ? ? ? ? ? ? ? ? ? ? ? ? if (headerNames != null) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? while (headerNames.hasMoreElements()) { ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? String name = headerNames.nextElement(); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? String values = request.getHeader(name); ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? template.header(name, values); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? }? }? ? @Component @FeignClient(value = "abc",fallback = abcServiceHystric.class ,configuration = FeignConfiguration.class) public interface AbcService { }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)簡單的webservice方式
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡單的webservice方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Java基礎(chǔ)知識之StringWriter流的使用
這篇文章主要介紹了Java基礎(chǔ)知識之StringWriter流的使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12idea中使用maven?archetype新建項(xiàng)目時(shí)卡住問題解決方案
這篇文章主要介紹了idea中使用maven?archetype新建項(xiàng)目時(shí)卡住,解決本問題的方法,就是在maven的runner加上參數(shù)-DarchetypeCatalog=local就可以了,不需要下載xml文件再放到指定目錄,需要的朋友可以參考下2023-08-08詳解JVM的內(nèi)存對象介紹[創(chuàng)建和訪問]
這篇文章主要介紹了JVM的內(nèi)存對象介紹[創(chuàng)建和訪問],文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03MybatisPlus?LambdaQueryWrapper使用int默認(rèn)值的坑及解決
這篇文章主要介紹了MybatisPlus?LambdaQueryWrapper使用int默認(rèn)值的坑及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。2022-01-01Spring實(shí)戰(zhàn)之Bean銷毀之前的行為操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之Bean銷毀之前的行為操作,結(jié)合實(shí)例形式分析了spring在bean銷毀之前的行為相關(guān)設(shè)置與使用技巧,需要的朋友可以參考下2019-11-11Java for-each循環(huán)使用難題2例(高級使用方法)
從Java5起,在Java中有了for-each循環(huán),可以用來循環(huán)遍歷collection和array。For each循環(huán)允許你在無需保持傳統(tǒng)for循環(huán)中的索引,或在使用iterator /ListIterator時(shí)無需調(diào)用while循環(huán)中的hasNext()方法就能遍歷collection2014-04-04RocketMQ?offset確認(rèn)機(jī)制示例詳解
這篇文章主要為大家介紹了RocketMQ?offset確認(rèn)機(jī)制示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09