【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, https://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, https://api.examplebank.com);

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

java BasicAuthRequestInterceptor Bank bank = Feign.builder() .decoder(accountDecoder) .requestInterceptor(new BasicAuthRequestInterceptor(username, password)) .target(Bank.class, https://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; } }

「其他文章」