翻譯: Spring Cloud Feign使用文檔

轉載請註明出處: 翻譯: Spring Cloud Feign使用文檔html

Why Feign and not X?

Feign使用諸如JerseyCXF之類的工具來實現ReSTSOAP服務的java客戶端, 此外, Feign容許你在http庫(如: Apache HC)之上編寫本身的代碼. 經過自定義解碼器(decoders)和錯誤處理(error handing), Feign能夠用最小的開銷和最少的代碼將你的代碼關聯到任何基於文本的http接口(http APIS),java

How does Feign work?

Feign是經過將註解(annotations)轉換成模板請求來實現它的功能的, Feign能夠將請求參數直接應用到這些模板上. 儘管Feign只支持基於文本的接口, 但一樣的它能顯著地簡化系統的方方面面, 如請求重放等, 此外, Feign也可使你的單元測試更加簡單.git

Java Version Campatibility

Feign 10.x及以上的版本是基於Java 8構建的, 且應該一樣支持Java 九、十、11, 若是你須要在JDK 6的版本上使用的話, 請使用Feign 9.x版本.github

Basics

下面的代碼是適配Retrofit示例的用法:正則表達式

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}

public static class Contributor {
  String login;
  int contributions;
}

public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
  
    // Fetch and print a list of the contributors to this library.
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Interface Annotations

Feign的註解定義了接口與底層http客戶端功能之間的約定, 默認狀況下各個註解的約定含義以下:express

Annotation Interface Target Usage
@RequestLine 接口 定義請求的HttpMethodUriTemplate. 模板中可使用大括號包圍的表達式({expression}), 表達式的值由@Param對應參數的註解值提供.
@Param 參數 定義模板變量, 變量的值應該由名字相對應的表達式提供.
@Headers 方法、Type 定義HeaderTemplate; 使用@Param註解的值解析對應的表達式. 當該註解應用在Type上時, 該模板會被應用到每個請求上. 當該註解應用在方法上時, 該模板僅會被應用到對應的方法上.
@QueryMap 參數 將鍵值對類型的Map、POJO展開成地址上的請求參數(query string)
@HeaderMap 參數 將鍵值對類型的Map展開成請求頭Http Headers.
@Body 方法 定義與UriTemplateHeaderTemplate相似的模板(Template), 該模板可使用@Param的註解值解析對應的表達式

Templates and Expressions

Feign支持由URI Template - RFC 6570定義的簡單字符串(Level 1)表達式, 表達式的值從相關方法上對應@Param註解提供, 示例以下:json

public interface GitHub {
  
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> getContributors(@Param("owner") String owner, @Param("repo") String repository);
  
  class Contributor {
    String login;
    int contributions;
  }
}

public class MyApp {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
    
    /* The owner and repository parameters will be used to expand the owner and repo expressions
     * defined in the RequestLine.
     * 
     * the resulting uri will be https://api.github.com/repos/OpenFeign/feign/contributors
     */
    github.contributors("OpenFeign", "feign");
  }
}

表達式必須使用大括號({})包裹着, 而且支持使用冒號(:)分隔的正則表達式來限定表達式的值. 如限定上述例子的owner參數的值必須是字母: {owner:[a-zA-Z]*}.後端

Request Parameter Expansion

RequestLineQueryMap遵循 URI Template - RFC 6570規範對一級模板(Level 1 templates)的規定:api

  • 未被解析的值將會被忽略.
  • 全部未編碼或者經過@Param註解標記爲已編碼(encoded)的字符和變量值都使用pct編碼(pct-encoded).

能夠從Advanced Usage一節查看更多示例.安全

What about slashes? /

默認狀況下, @RequestLine@QueryMap模板不會對正斜槓/進行編碼, 若是須要默認對其進行編碼的話, 能夠將@RequestLinedecodeSlash屬性值設置爲false.

What about plus? +

根據URI規範, +可使用在URI地址和請求參數(query segments)這兩個部分上, 然而在請求參數(query)上對該符號的處理卻有可能不一致, 在一些遺留的系統上, +會被解析成一個空白符(space). 對此, Feign採用現代系統對+的解釋, 不會將+認爲是一個空白符(space), 並將請求參數上的+編碼爲%2B.

若是你但願將+當成空白符(space), 那麼請直接使用一個空格 或者直接將其編碼爲%20.

Custom Expansion

@Param註解有一個可選的參數expander能夠用來控制單個參數的展開行爲(expansion), 該屬性的值必須指向一個實現了Expander接口的類:

public interface Expander {
    String expand(Object value);
}

對該方法的返回值的處理與上述規則相同, 若是返回值是null或者是一個空字符串, 那麼該值會被忽略. 若是返回值不是使用pct編碼(pct-encoded)的, 將會自動轉換成pct編碼. 能夠從 Custom @Param Expansion 一節查看更多示例.

Request Headers Expansion

@HeadersHeaderMap模板對 Request Parameter Expansion 一節闡述的規則作如下修改, 並遵循之:

  • 未被解析的值將會被忽略, 但若是解析到一個空的值(empty header value), 那麼對應的請求頭會被移除.
  • 不會對請求頭使用pct編碼(pct-encoding).

能夠從Headers一節查看示例.

關於@Param參數和參數名須要注意的點

不管是在@RequestLine@QueryMap@BodyTemplate仍是@Headers上的表達式, 只要表達式內的變量名字相同, 那麼它們的值也必然相同. 以下面的例子, contentType的值會同時被解析到請求頭(header)和路徑(path)上:

public interface ContentService {
  @RequestLine("GET /api/documents/{contentType}")
  @Headers("Accept: {contentType}")
  String getDocumentByType(@Param("contentType") String type);
}

當你在設計你的接口的必定要牢記這一點.

Reuqest Body Expansion

Body模板對 Request Parameter Expansion 一節闡述的規則作如下修改, 並遵循之:

  • 未被解析的值將會被忽略.
  • 展開的值在被解析到請求體以前不會通過Encoder處理.
  • 必須指定Content-Type請求頭, 能夠從 Body Templates一節查看示例.

Customization

你能夠在不少地方對Feign進行定製. 好比, 你可使用Feign.builder()對自定義的組件構建API接口:

interface Bank {
  @RequestLine("POST /account/{id}")
  Account getAccountInfo(@Param("id") String id);
}

public class BankService {
  public static void main(String[] args) {
    Bank bank = Feign.builder().decoder(
        new AccountDecoder())
        .target(Bank.class, "https://api.examplebank.com");
  }
}

Multiple Interfaces

Feign客戶以對使用Target<T>(默認是HardCodedTarget<T>)定義的對象生成多個API接口, 這樣你能夠在執行前動態發現服務或者對請求進行裝飾.

例如, 下面的代碼能夠實現爲從身份服務中獲取當前url受權令牌(auth token), 而後設置到每一個請求上:

public class CloudService {
  public static void main(String[] args) {
    CloudDNS cloudDNS = Feign.builder()
      .target(new CloudIdentityTarget<CloudDNS>(user, apiKey));
  }
  
  class CloudIdentityTarget extends Target<CloudDNS> {
    /* implementation of a Target */
  }
}

Examples

Feign包含了GitHubWikipedia的客戶端示例代碼, 在實踐中也能夠參考這些項目, 尤爲是example daemon.


Integrations

Feign在設計上就但願可以和其餘開源項目很好的整合到一塊兒, 咱們也很樂於將你喜歡的模塊添加進來.

Gson

Gson包含了和JSON接口相關的編碼(GsonEncoder)、解碼器(GsonDecoder), 將它將它用到Feign.Builder的方式以下:

public class Example {
  public static void main(String[] args) {
    GsonCodec codec = new GsonCodec();
    GitHub github = Feign.builder()
                         .encoder(new GsonEncoder())
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
  }
}

Jackson

Jackson包含了和JSON接口相關的編碼(JacksonEncoder)、解碼器(JacksonDecoder), 將它將它用到Feign.Builder的方式以下:

public class Example {
  public static void main(String[] args) {
      GitHub github = Feign.builder()
                     .encoder(new JacksonEncoder())
                     .decoder(new JacksonDecoder())
                     .target(GitHub.class, "https://api.github.com");
  }
}

Sax

SaxDecoder提供了能夠與普通JVM和Android環境兼容的方式解析XML文本, 下面的例子展現瞭如何使用:

public class Example {
  public static void main(String[] args) {
      Api api = Feign.builder()
         .decoder(SAXDecoder.builder()
                            .registerContentHandler(UserIdHandler.class)
                            .build())
         .target(Api.class, "https://apihost");
    }
}

JAXB

JAXB包含了和XML接口相關的編碼器(JAXBEncoder)、解碼器(JAXBEncoder), 將它將它用到Feign.Builder的方式以下:

public class Example {
  public static void main(String[] args) {
    Api api = Feign.builder()
             .encoder(new JAXBEncoder())
             .decoder(new JAXBDecoder())
             .target(Api.class, "https://apihost");
  }
}

JAX-RS

JAXRSContract使用JAX-RS規範提供的標準覆蓋了對註解的處理, 目前實現的是1.1版的規範, 示例以下:

interface GitHub {
  @GET @Path("/repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@PathParam("owner") String owner, @PathParam("repo") String repo);
}

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                       .contract(new JAXRSContract())
                       .target(GitHub.class, "https://api.github.com");
  }
}

OkHttp

OkHttpClient直接將Feign的http請求直接交由OkHttp處理, 後者實現了SPDY協議和提供了更好的網絡控制能力.

OkHttp整合到Feign中須要你把OkHttp模塊放到classpath下, 而後作以下配置:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .client(new OkHttpClient())
                     .target(GitHub.class, "https://api.github.com");
  }
}

Ribbon

RibbonClient會覆蓋Feign客戶端的URL解析, 以實現由Ribbon提供的智能路由和彈性能力.

RibbonFeign整合須要你將url中的主機名(host)部分替換成Ribbon客戶端名. 例如Ribbon客戶端明爲myAppProd:

public class Example {
  public static void main(String[] args) {
    MyService api = Feign.builder()
          .client(RibbonClient.create())
          .target(MyService.class, "https://myAppProd");
  }
}

Java 11 Http2

Http2Client直接將Feign的http請求交給Java11 New HTTP/2 Client處理, 後者實現了HTTP/2協議.

要將New HTTP/2 ClientFeign整合使用, 你須要使用Java SDK 11, 並作以下配置:

GitHub github = Feign.builder()
                     .client(new Http2Client())
                     .target(GitHub.class, "https://api.github.com");

Hystrix

HystrixFeign實現了由Hystrix提供的斷路器功能.

要將HystrixFeign整合, 你須要將Hystrix模塊放到classpath下, 並使用HystrixFeign:

public class Example {
  public static void main(String[] args) {
    MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd");
  }
}

SOAP

SOAP包含了XML接口相關的編碼器(SOAPEncoder)、解碼器(SOAPDecoder).

該模塊經過JAXB和SOAPMessage實現了對SOAP Body的編碼和解碼的支持, 經過將SOAPFault包裝秤javax.xml.ws.soap.SOAPFaultException實現了對SOAPFault解碼的功能, 所以, 對於SOAPFault的處理, 你只須要捕獲SOAPFaultException.

使用示例以下:

public class Example {
  public static void main(String[] args) {
    Api api = Feign.builder()
         .encoder(new SOAPEncoder(jaxbFactory))
         .decoder(new SOAPDecoder(jaxbFactory))
         .errorDecoder(new SOAPErrorDecoder())
         .target(MyApi.class, "http://api");
  }
}

若是SOAP Faults的響應使用了表示錯誤的狀態碼(4xx, 5xx, …)的話, 那麼你還須要添加一個SOAPErrorDecoder.

SLF4J

SLF4JModule實現了將Feign的日誌重定向到SLF4J, 這容許你很容易的就能使用你想用的日誌後端(Logback、Log4J等).

要將SLF4JFeign整合, 你須要將SLF4J模塊和對應的日誌後端模塊放到classpath下, 並作以下配置:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .logger(new Slf4jLogger())
                     .target(GitHub.class, "https://api.github.com");
  }
}

Decoders

Feign.builder()容許你手動指定額外的配置, 如配置如何對響應進行解析.

若是你接口定義的方法的返回值是除了ResponseStringbyte[]void以外的類型, 那麼你必須配置一個非默認的Decoder.

下面的代碼展現瞭如何配置使用feign-gson對JSON解碼:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .decoder(new GsonDecoder())
                     .target(GitHub.class, "https://api.github.com");
  }
}

若是你想在對響應進行解碼以前先對其作處理的話, 你可使用mapAndDecode方法, 下面的代碼展現了對一個jsonp響應的處理, 在將響應交給JSON解碼器以前, 須要先對jsonp作處理:

public class Example {
  public static void main(String[] args) {
    JsonpApi jsonpApi = Feign.builder()
                         .mapAndDecode((response, type) -> jsopUnwrap(response, type), new GsonDecoder())
                         .target(JsonpApi.class, "https://some-jsonp-api.com");
  }
}

Encoders

將一個請求體發送到服務器的最簡單的辦法是定義一個POST請求方法, 該方法的參數類型是Stringbyte[], 且參數上不帶任何註解, 而且你可能還須要設置Content-Type請求頭(若是沒有的話):

interface LoginClient {
  @RequestLine("POST /")
  @Headers("Content-Type: application/json")
  void login(String content);
}

public class Example {
  public static void main(String[] args) {
    client.login("{\"user_name\": \"denominator\", \"password\": \"secret\"}");
  }
}

而經過配置Encoder, 你能夠發送一個類型安全的請求體, 下面的例子展現了使用feign-gson擴展來實現編碼:

static class Credentials {
  final String user_name;
  final String password;

  Credentials(String user_name, String password) {
    this.user_name = user_name;
    this.password = password;
  }
}

interface LoginClient {
  @RequestLine("POST /")
  void login(Credentials creds);
}

public class Example {
  public static void main(String[] args) {
    LoginClient client = Feign.builder()
                              .encoder(new GsonEncoder())
                              .target(LoginClient.class, "https://foo.com");
    
    client.login(new Credentials("denominator", "secret"));
  }
}

@Body templates

使用@Body註解的模板會使用@Param註解的值來展開模板內部的表達式, 對於POST請求你可能還須要設置Content-Type請求頭(若是沒有的話):

interface LoginClient {

  @RequestLine("POST /")
  @Headers("Content-Type: application/xml")
  @Body("<login \"user_name\"=\"{user_name}\" \"password\"=\"{password}\"/>")
  void xml(@Param("user_name") String user, @Param("password") String password);

  @RequestLine("POST /")
  @Headers("Content-Type: application/json")
  // json curly braces must be escaped!
  @Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
  void json(@Param("user_name") String user, @Param("password") String password);
}

public class Example {
  public static void main(String[] args) {
    client.xml("denominator", "secret"); // <login "user_name"="denominator" "password"="secret"/>
    client.json("denominator", "secret"); // {"user_name": "denominator", "password": "secret"}
  }
}

Headers

Feign支持在api上爲每一個請求設置請求頭, 也支持爲每一個客戶端的請求設置請求頭, 你能夠根據實際場景進行選擇.

Set headers using apis

對於那些明確須要設置某些請求頭的接口的狀況, 適用於將請求頭的定義做爲接口的一部分.

靜態配置的請求頭能夠經過在接口上使用@Headers註解設置:

@Headers("Accept: application/json")
interface BaseApi<V> {
  @Headers("Content-Type: application/json")
  @RequestLine("PUT /api/{key}")
  void put(@Param("key") String key, V value);
}

也能夠在方法上的@Headers使用變量展開動態指定請求頭的內容:

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

有時候, 對於同一個接口或客戶端的請求頭, 其鍵和值可能會隨着不一樣的方法調用而發生變化, 且不可預知(例如: 自定義元數據請求頭字段"x-amz-meta-"或"x-goog-meta-"), 此時能夠在接口上聲明一個Map參數, 並使用@HeaderMap註解將Map的內容設置爲對應請求的請求頭:

public interface Api {
   @RequestLine("POST /")
   void post(@HeaderMap Map<String, Object> headerMap);
}

上述的幾個方法均可以在接口上指定請求的請求頭, 且不須要在構造時對Feign客戶端作任何的定製.

Setting headers per target

當同一個接口的請求須要針對不一樣的請求對象(endpoints)配置不一樣的請求頭, 或者須要對同一個接口的每一個請求都定製其請求頭時, 能夠在Feign客戶端上使用RequestInterceptorTarget來設置請求頭.

使用RequestInterceptor設置請求頭的例子能夠在Request Interceptor一節中查看示例.

使用Target設置請求頭的示例以下:

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();
    }
  }
  
  public class Example {
    public static void main(String[] args) {
      Bank bank = Feign.builder()
              .target(new DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider));
    }
  }

上述方法的最終效果取決於你對RequestInterceptorTarget內部的實現, 能夠經過這種方法對每一個Feign客戶端的全部接口調用設置請求頭. 這在一些場景下是很是有用的, 如對每一個Feign客戶端的全部請求設置認證令牌authentication token. 這些方法是在接口調用者所在的線程中執行的(譯者注: 須要注意線程安全), 所以請求頭的值能夠是在調用時根據上下文動態地設置. 例如, 能夠根據不一樣的調用線程, 從ThreadLocal裏讀取不一樣的數據設置請求頭.

Advanced usage

Base Apis

大多數狀況下服務的接口都遵循相同的約定. Feign使用單繼承的方式來實現, 好比下面的例子:

interface BaseAPI {
  @RequestLine("GET /health")
  String health();

  @RequestLine("GET /all")
  List<Entity> all();
}

你能夠經過繼承的方式來擁有BaseAPI的接口, 並實現其餘特定的接口:

interface CustomAPI extends BaseAPI {
  @RequestLine("GET /custom")
  String custom();
}

不少時候, 接口對資源的表示也是一致的, 所以, 也能夠在基類的接口中使用泛型參數:

@Headers("Accept: application/json")
interface BaseApi<V> {

  @RequestLine("GET /api/{key}")
  V get(@Param("key") String key);

  @RequestLine("GET /api")
  List<V> list();

  @Headers("Content-Type: application/json")
  @RequestLine("PUT /api/{key}")
  void put(@Param("key") String key, V value);
}

interface FooApi extends BaseApi<Foo> { }

interface BarApi extends BaseApi<Bar> { }

Logging

你能夠經過爲Feign客戶端設置Logger來記錄其http日誌, 最簡單的實現以下:

public class Example {
  public static void main(String[] args) {
    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

若是你須要跨Feign客戶端對全部請求都作修改, 那麼你能夠配置RequestInterceptor來實現. 例如, 若是你是請求的一個代理, 那麼你可能會須要設置X-Forwarded-For請求頭:

static class ForwardedForInterceptor implements RequestInterceptor {
  @Override public void apply(RequestTemplate template) {
    template.header("X-Forwarded-For", "origin.host.com");
  }
}

public class Example {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
                 .decoder(accountDecoder)
                 .requestInterceptor(new ForwardedForInterceptor())
                 .target(Bank.class, "https://api.examplebank.com");
  }
}

另外一個常見的使用攔截器的場景是受權, 好比使用內置的BasicAuthRequestInterceptor:

public class Example {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
                 .decoder(accountDecoder)
                 .requestInterceptor(new BasicAuthRequestInterceptor(username, password))
                 .target(Bank.class, "https://api.examplebank.com");
  }
}

Custom @Param Expansion

使用@Param註解的參數會用其toString()方法展開得到參數值, 也能夠經過制定一個自定義的Param.Expander來控制. 如對日期的格式化:

public interface Api {
  @RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date);
}

Dynamic Query Parameters

能夠經過對Map類型的參數加上QueryMap註解, 將Map的內容構形成查詢參數(query parameters):

public interface Api {
  @RequestLine("GET /find")
  V find(@QueryMap Map<String, Object> queryMap);
}

一樣的, 也能夠經過使用QueryMapEncoder實現用POJO對象生成查詢參數(query parameter):

public interface Api {
  @RequestLine("GET /find")
  V find(@QueryMap CustomPojo customPojo);
}

當用這種方式時, 若是沒有指定一個自定義的QueryMapEncoder, 那麼查詢參數的(query parameter)內容將根據對象的成員變量生成, 參數名對應變量名. 下面的例子中, 根據POJO對象生成的查詢參數(query parameter)的內容是"/find?name={name}&number={number}", 生成的查詢參數的順序是不固定的, 按照慣例, 若是POJO對象的某個變量值爲null, 那麼該變量會被丟棄.

public class CustomPojo {
  private final String name;
  private final int number;

  public CustomPojo (String name, int number) {
    this.name = name;
    this.number = number;
  }
}

設置自定義QueryMapEncoder的方式以下:

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .queryMapEncoder(new MyCustomQueryMapEncoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

當用@QueryMao註解時, 默認的編碼器(encoder)會對對象的字段使用反射來將其展開成查詢參數(query string). 若是但願經過對象的getter和setter方法來展開查詢參數(query string), 請使用BeanQueryMapEncoder:

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .queryMapEncoder(new BeanQueryMapEncoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

Error Handling

你能夠經過在Feign實例構造時註冊一個自定義的ErrorDecoder來實現對非正常響應的控制:

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .errorDecoder(new MyErrorDecoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

全部HTTP狀態碼不爲2xx的響應都會觸發ErrorDecoderdecode方法, 在這個方法內你能夠對這些響應針對性地拋出異常, 或作其餘額外的處理. 若是但願對請求進行重試, 那麼能夠拋出RetryableException, 該異常會觸發Retryer.

Retry

默認狀況下, Feign會對產生IOException的請求自動重試, 不管使用的是哪一種HTTP方法, 都認爲IOExcdeption是由短暫的網絡問題產生的. 對ErrorDecoder內拋出的RetryableException也會進行請求重試. 你也能夠通在Feign實例構造時設置自定義的Retryer來定製重試行爲:

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .retryer(new MyRetryer())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

Retryer的實現須要決定一個請求是否應該進行重試, 能夠經過continueOrPropagate(RetryableException e)方法的返回值(truefalse)來實現. 每一個Feign客戶端執行時都會構造一個Retryer實例, 這樣的話你能夠維護每一個請求的從新狀態.

若是最終重試也失敗了, 那麼會拋出RetryException, 若是但願拋出致使重試失敗的異常, 能夠在構造Feign客戶端時指定exceptionPropagationPolicy()選項.

Static and Default Methods

使用Feign的接口多是靜態的或默認的方法(Java 8及以上支持), 這容許Feign客戶端包含一些不適用底層接口定義的邏輯. 例如, 使用靜態方法能夠很輕易地指定通用客戶端構造配置, 使用默認方法能夠用於組合查詢或定義默認參數:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("GET /users/{username}/repos?sort={sort}")
  List<Repo> repos(@Param("username") String owner, @Param("sort") String sort);

  default List<Repo> repos(String owner) {
    return repos(owner, "full_name");
  }

  /**
   * Lists all contributors for all repos owned by a user.
   */
  default List<Contributor> contributors(String user) {
    MergingContributorList contributors = new MergingContributorList();
    for(Repo repo : this.repos(owner)) {
      contributors.addAll(this.contributors(user, repo.getName()));
    }
    return contributors.mergeResult();
  }

  static GitHub connect() {
    return Feign.builder()
                .decoder(new GsonDecoder())
                .target(GitHub.class, "https://api.github.com");
  }
}
相關文章
相關標籤/搜索