您好,登錄后才能下訂單哦!
本篇內容介紹了“Spring Cloud Feign怎么添加自定義Header”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
最近在調用一個接口,接口要求將token放在header中傳遞。由于我的項目使用了feign, 那么給請求中添加 header 就必須要去feign中找方法了。
在給 @FeignClient 注解的接口生成代理對象的時候,有這么一段:
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { @Override public Object getObject() throws Exception { return getTarget(); } // getTarget() 最終會調用到 configureUsingConfiguration() protected void configureUsingConfiguration(FeignContext context,Feign.Builder builder) { Map<String, RequestInterceptor> requestInterceptors = context.getInstances(this.contextId, RequestInterceptor.class); if (requestInterceptors != null) { builder.requestInterceptors(requestInterceptors.values()); } ... } }
生成代理類時,會使用到 spring 上下文的 RequestInterceptor, 而 @FeignClient 的代理類在執行的時候,會去使用該攔截器:
final class SynchronousMethodHandler implements MethodHandler { Request targetRequest(RequestTemplate template) { for (RequestInterceptor interceptor : requestInterceptors) { interceptor.apply(template); } return target.apply(template); } }
所以自定義自己的攔截器,然后注入到 spring 上下文中,這樣就可以在請求的上下文中添加自定義的請求頭:
@Service public class MyRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.header("my-header","header"); } }
實現簡單,使用現有接口注入即可
操作的是全局的 RequestTemplate,比較難以根據不同的服務方提供不同的 header。 雖然可以在 template 中根據 uri 來判斷不同的服務提供方,然后添加對應的header,但是憑空多了很多配置信息,維護也比較困難。
既然我們用到了 openfeign 框架,那我們找找 openfeign 官方是怎么解決的(https://github.com/OpenFeign/feign):
// openfeign 官方文檔 public interface ContentService { @RequestLine("GET /api/documents/{contentType}") @Headers("Accept: {contentType}") String getDocumentByType(@Param("contentType") String type); }
通過上述官方代碼示例,我們可以發現,其實使用原生的 API 就可以滿足我們的需求:
@FeignClient(name = "feign",url = "127.0.0.1:8080") public interface FeignTest { @RequestMapping(value = "/test") @Headers({"app: test-app","token: ${test-app.token}"}) String test(); }
然而比較遺憾的是,@Headers 并沒有生效,生成的RequestTemplate中,沒有上述兩個 Header 信息。 跟蹤代碼,我們發現,ReflectFeign在生成遠程服務的代理類的時候,會通過 Contract 接口準備數據。 而*@Headers* 注解沒有生效的原因是:官方的 Contract 沒有生效:
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { protected Feign.Builder feign(FeignContext context) { Feign.Builder builder = get(context, Feign.Builder.class) // required values .logger(logger) .encoder(get(context, Encoder.class)) .decoder(get(context, Decoder.class)) .contract(get(context, Contract.class)); ... } }
對于 springcloud-openfeign 來說,在創建 Feign 相關類的時候,使用的是容器中注入的 Contract:
@Bean @ConditionalOnMissingBean public Contract feignContract(ConversionService feignConversionService) { return new SpringMvcContract(this.parameterProcessors, feignConversionService); } public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware { @Override public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) { .... // 注意這里,它只取了 RequestMapping 注解 RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class); .... parseHeaders(md, method, classAnnotation); } return md; } }
到這里我們就梳理出來整個事情的來龍去脈了:
openfeign 是支持給方法加上自定義 header 的,它用的是自己的注解 @Headers
springcloud-openfeign 使用了 openfeign的核心功能,但是關于 @Headers 的注解沒有使用
springcloud 使用了自己的 SpringMvcContract 來處理請求的相關資源信息,里面只使用 @RequestMapping 注解
我們比較容易想到的是,既然 @RequestMapping 注解中有 headers 的屬性,我們可以試一下
@FeignClient(name = "server",url = "127.0.0.1:8080") public interface FeignTest { @RequestMapping(value = "/test",headers = {"app=test-app","token=${test-app.token}"}) String test(); }
親測可用,這樣我們就可以給特定的服務單獨定制頭信息啦。
實現更加簡單了,甚至都不用自己實現接口,只需要自己在相關注解中增加對應屬性配置即可
雖然不用給全局的請求增加header,但是對于相同的服務方,卻要在每個@RequestMapping注解中添加相同的header配置,會比較麻煩,能否添加全局的呢?
通過 SpringMvcContract 代碼我們也很容易發現,對于類的注解,它只會處理 RequestMapping,其它也都忽略了。 那么如果我們重新定義自己的 Contract,就可以隨心所欲實現自己的想要的功能啦。
方便起見,我們直接復用 openfeign 的 @Header
簡單起見,我們直接繼承 SpringMvcContract
自定義自己的 Contract,然后注入到 spring 上下文中
/** * 為了處理簡單,我們直接繼承 SpringMvcContract */ @Service public class MyContract extends SpringMvcContract { /** * 該屬性是為了使用 springcloud config */ private ResourceLoader resourceLoader; @Override protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) { //這里復用原有 SpringMvcContract 邏輯 super.processAnnotationOnClass(data, clz); // 以下是新加的邏輯(其實是使用的 openfeign 自帶的 Contract.Default的邏輯) if (clz.isAnnotationPresent(Headers.class)) { String[] headersOnType = clz.getAnnotation(Headers.class).value(); Map<String, Collection<String>> headers = toMap(headersOnType); headers.putAll(data.template().headers()); data.template().headers(null); // to clear data.template().headers(headers); } } private Map<String, Collection<String>> toMap(String[] input) { Map<String, Collection<String>> result = new LinkedHashMap<>(input.length); for (String header : input) { int colon = header.indexOf(':'); String name = header.substring(0, colon); if (!result.containsKey(name)) { result.put(name, new ArrayList<>(1)); } result.get(name).add(resolve(header.substring(colon + 1).trim())); } return result; } private String resolve(String value) { if (StringUtils.hasText(value) && resourceLoader instanceof ConfigurableApplicationContext) { return ((ConfigurableApplicationContext) this.resourceLoader).getEnvironment() .resolvePlaceholders(value); } return value; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; // 注意,因為SpringMvcContract 也使用了 resourceLoader,所以必須給它指定解析器,否則不會解析占位符 super.setResourceLoader(resourceLoader); } }
使用的時候直接對 接口做 header的配置即可:
@Headers({"app: test-app","token: ${test-app.token}"}) public interface FeignTest { @RequestMapping(value = "/test") String test(); }
可以根據自己的需要自由定義
自定義帶來一定的學習成本,而且因為是直接繼承 spring 的實現,為以后升級留下隱患
聰明的讀者也許在方案二的結尾就能反應過來:springcloud 支持*@RequestMapping*注解的 header,而該注解完全可以用在類上面!
@FeignClient(name = "feign",url = "127.0.0.1:8080") @RequestMapping(value = "/",headers = {"app=test-app","token=${test-app.token}"}) public interface FeignTest { @RequestMapping(value = "/test") String test(); }
完全不用自定義,原生支持
基本沒有。 可能對于有些不習慣在類上使用 @RequestMapping 注解的同學來說,有點強迫癥,不過基本可以忽略
思維定勢,工作內容問題,很少會在feign接口上使用 @RequestMapping
SpringMvcContract 中的 processAnnotationOnClass 方法中沒有關于對 header的處理,導致一開始忽略這個
SpringMvcContract 是在 parseAndValidatateMetadata 中解決類上面的 header 的問題
“Spring Cloud Feign怎么添加自定義Header”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。