【Fegin技術專題】「原生態」開啟Fegin之RPC技術的開端,你會使用原生態的Fegin嗎?(高階用法)

語言: CN / TW / HK

theme: smartblue

本文已參與「掘力星計劃」,贏取創作大禮包,挑戰創作激勵金。

對於Httpclient請求機制進行設定操作處理。

@Body請求體模板

@Body註解申明一個請求體模板,模板中可以帶有引數,與方法中@Param註解申明的引數相匹配,使用方法如下:

java interface LoginClient { @RequestLine("POST /") @Headers("Content-Type: application/json") // json curly braces must be escaped! // 這裡JSON格式需要的花括號居然需要轉碼,有點蛋疼了。 @Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D") void login(@Param("user_name") String user, @Param("password") String password); } ... client.login("denominator", "secret"); // {"user_name": "denominator", "password": "secret"}

Headers請求頭

Feign支援給請求的api設定或者請求的客戶端設定請求頭,如下:

給API設定請求頭

使用 @Headers 設定靜態請求頭

java // 給BaseApi中的所有方法設定Accept請求頭 @Headers("Accept: application/json") interface BaseApi<V> { // 單獨給put方法設定Content-Type請求頭 @Headers("Content-Type: application/json") @RequestLine("PUT /api/{key}") void put(@Param("key") String, V value); }

設定動態值的請求頭

java @RequestLine("POST /") @Headers("X-Ping: {token}") void post(@Param("token") String token);

設定key和value都是動態的請求頭

呼叫時動態確定使用不同的請求頭

可以使用 @HeaderMap 註解,如下:

java // @HeaderMap 註解設定的請求頭優先於其他方式設定的 @RequestLine("POST /") void post(@HeaderMap Map<String, Object> headerMap);

給Target設定請求頭

有時我們需要在一個API實現中根據不同的endpoint來傳入不同的Header,這個時候我們可以使用自定義的RequestInterceptor 或 Target來實現.

通過自定義的 RequestInterceptor 來實現請檢視 Request Interceptors

下面是一個通過自定義Target來實現給每個Target設定安全校驗資訊Header的例子:

java static class DynamicAuthTokenTarget<T> implements Target<T> { public DynamicAuthTokenTarget(Class<T> clazz, UrlAndTokenProvider provider, ThreadLocal<String> requestIdProvider); ... @Override public Request apply(RequestTemplate input) { TokenIdAndPublicURL urlAndToken = provider.get(); if (input.url().indexOf("http") != 0) { input.insert(0, urlAndToken.publicURL); } input.header("X-Auth-Token", urlAndToken.tokenId); input.header("X-Request-ID", requestIdProvider.get()); return input.request(); } } ... Bank bank = Feign.builder() .target(new DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider));

  • 這種方法的實現依賴於給Feign 客戶端設定的自定義的RequestInterceptor 或 Target。可以被用來給一個客戶端的所有api請求設定請求頭。比如說可是被用來在header中設定身份校驗資訊。這些方法是線上程執行api請求的時候才會執行,所以是允許在執行時根據上下文來動態設定header的。

  • 比如說可以根據執行緒本地儲存(thread-local storage)來為不同的執行緒設定不同的請求頭。

Base APIS

有些請求中的一些方法是通用的,但是可能會有不同的引數型別或者返回型別,這個時候可以這麼用:

```java // 通用API interface BaseAPI { @RequestLine("GET /health") String health(); @RequestLine("GET /all") List all(); } // 繼承通用API interface CustomAPI extends BaseAPI { @RequestLine("GET /custom") String custom(); } // 各種型別有相同的表現形式,定義一個統一的API @Headers("Accept: application/json") interface BaseApi { @RequestLine("GET /api/{key}") V get(@Param("key") String key); @RequestLine("GET /api") List list(); @Headers("Content-Type: application/json") @RequestLine("PUT /api/{key}") void put(@Param("key") String key, V value); } // 根據不同的型別來繼承 interface FooApi extends BaseApi { } interface BarApi extends BaseApi { }

```

Logging

你可以通過設定一個 Logger 來記錄http訊息,如下:

java GitHub github = Feign.builder() decoder(new GsonDecoder()) .logger(new Logger.JavaLogger().appendToFile("logs/http.log")) .logLevel(Logger.Level.FULL) .target(GitHub.class, http://api.github.com);

Request Interceptors

當你希望修改所有的的請求的時候,你可以使用Request Interceptors。比如說,你作為一箇中介,你可能需要為每個請求設定 X-Forwarded-For

java static class ForwardedForInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.header("X-Forwarded-For", "origin.host.com"); } } ... Bank bank = Feign.builder() .decoder(accountDecoder) .requestInterceptor(new ForwardedForInterceptor()) .target(Bank.class, http://api.examplebank.com);

或者,你可能需要實現Basic Auth,這裡有一個內建的基礎校驗攔截器

java BasicAuthRequestInterceptor Bank bank = Feign.builder() .decoder(accountDecoder) .requestInterceptor(new BasicAuthRequestInterceptor(username, password)) .target(Bank.class, http://api.examplebank.com);

@Param Expansion

@Param 註解給模板中的引數設值的時候,預設的是使用的物件的 toString() 方法的值,通過宣告 自定義的Param.Expander,使用者可以控制其行為,比如說格式化 Date 型別的值:

java // 通過設定 @Param 的 expander 為 DateToMillis.class 可以定義Date型別的值 @RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date);

Dynamic Query Parameters

動態查詢引數支援,通過使用 @QueryMap 可以允許動態傳入請求引數,如下:

java @RequestLine("GET /find") V find(@QueryMap Map<String, Object> queryMap);

自定義註解掃描動態生成客戶端

原生Feign只能一次解析一個介面,生成對應的請求代理物件,如果一個包裡有多個呼叫介面就要多次解析非常麻煩。

擴充套件BeanFactoryPostProcessor介面、

自定義註解:在掃描介面的過程中,可以通過一個自定義註解,來區分Feign介面並且指定呼叫的服務Url

實現擴充套件容器

java @Component public class FeignClientRegister implements BeanFactoryPostProcessor{ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { List<String> classes = scan(scanPath); if(classes==null){ return ; } Feign.Builder builder = getFeignBuilder(); if(classes.size()>0){ for (String claz : classes) { Class<?> targetClass = null; try { targetClass = Class.forName(claz); String url=targetClass.getAnnotation(FeignApi.class).serviceUrl(); if(url.indexOf("http://")!=0){ url="http://"+url; } Object target = builder.target(targetClass, url); beanFactory.registerSingleton(targetClass.getName(), target); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } } } public Feign.Builder getFeignBuilder(){ Feign.Builder builder = Feign.builder() .encoder(new JacksonEncoder()) .decoder(new JacksonDecoder()) .options(new Request.Options(1000, 3500)) .retryer(new Retryer.Default(5000, 5000, 3)); return builder; } public List<String> scan(String path){ ScanResult result = new FastClasspathScanner(path).matchClassesWithAnnotation(FeignApi.class, (Class<?> aClass) -> { }).scan(); if(result!=null){ return result.getNamesOfAllInterfaceClasses(); } return null; } }

「其他文章」