Feign 官方文檔翻譯

Feign 讓編寫java http客戶端變簡(jiǎn)單

Fegin是一個(gè)java調(diào)用HTTP的客戶端binder,其靈感來(lái)自于Retrofit、JAXRS-2.0WebSocket。Feign的首要目標(biāo)是降低將Denominator統(tǒng)一的綁定到HTTP的復(fù)雜性,而無(wú)需關(guān)心是否為RESTfull.

Why Feign and not X?

Feign的使用方式與Jersey和CXF編寫java client來(lái)調(diào)用REST或SOAP服務(wù)類似。此外,F(xiàn)eign還允許編寫基于Apache HC之類的類庫(kù)的代碼。Feign通過(guò)自定義decoder和error處理以最小的成本將你的代碼連接到http,并可以使用到任何基于文本的http API上。

Feign如何工作?

Feign基于將注解轉(zhuǎn)換成請(qǐng)求模板的方式工作。參數(shù)會(huì)簡(jiǎn)單直接的應(yīng)用到模板上。盡管Feign只支持基于文本的API,但是它的簡(jiǎn)化了類似于請(qǐng)求重放等系統(tǒng)層面的開發(fā)。此外,F(xiàn)eign讓單元測(cè)試更加方便。

Java 版本兼容

Feign 10.x版本基于java 1.8同時(shí)可以在java9,10和11版本上工作。如果要在JDK6上使用,請(qǐng)使用Feign 9.x

基本使用方式

下面是一個(gè)典型的用法,適配Retrofit示例。

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

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

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

public static class Issue {
  String title;
  String body;
  List<String> assignees;
  int milestone;
  List<String> labels;
}

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 + ")");
    }
  }
}

接口注解

Feign的注解定義了接口及其client如何工作的 Contract,F(xiàn)eign默認(rèn)Contract 如下表。

Annotation Interface Target Usage
@RequestLine Method 用來(lái)定義request 的 HttpMethodUriTemplate。表達(dá)式(expression)使用大括號(hào){}括起來(lái),表達(dá)式的值會(huì)由被@Param注解的參數(shù)提供。
@Param Parameter 定義模板變量,變量的值會(huì)根據(jù)參數(shù)名解析到Expression內(nèi)。
@Headers Method或Interface 定義HeaderTemplate,表達(dá)式的值會(huì)由被@Param注解的參數(shù)提供。@Header可以使用到MethodInterface上,當(dāng)作用到Interface上時(shí)Interface內(nèi)的所有方法都會(huì)使用該Header,如果作用到Method上那只會(huì)對(duì)該方法使用Header。
@QueryMap Parameter 可以修飾K-V結(jié)構(gòu)的Map或POJO,用以擴(kuò)展查詢參數(shù)。
@HeaderMap Parameter 可以修飾K-V結(jié)構(gòu)的Map或POJO,用以擴(kuò)展Http Header
@Body Method UriTemplateHeaderTemplate類似,用來(lái)定義一個(gè)Template,表達(dá)式的值會(huì)由被@Param注解的參數(shù)提供。

表達(dá)式(Templates)與模板(Expressions)

Feign的Expressions遵循URI Template - RFC 6570 level 1規(guī)范。Expressions需要配合Param所修飾的方法參數(shù)進(jìn)行擴(kuò)展。

Example

public interface GitHub {
  
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@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");
  }
}
  • 表達(dá)式(Expression):被花括號(hào)括起來(lái)的內(nèi)容,可以使用正則表達(dá)式,表達(dá)式名稱與正則表達(dá)式用冒號(hào):分隔,比如{owner:[a-zA-Z]*}。表達(dá)式的值由被@Param修飾的變量提供。
  • 模板(Template):多個(gè)表達(dá)式的集合,如GET /repos/{owner}/{repo}/contributors

請(qǐng)求參數(shù)說(shuō)明

RequestLineQueryMap 的模板遵循URI Template - RFC 6570 level 1規(guī)范,具體的規(guī)范如下:

  • 忽略掉無(wú)法解析的表達(dá)式
  • 如果沒(méi)有對(duì)字符串或變量進(jìn)行編碼或未通過(guò)@Param注解期編碼方式,默認(rèn)會(huì)使用pct-encoded編碼。

未定義的值和空值

未定義的表達(dá)式也是合法的表達(dá)式,如果明確指定表達(dá)式的值為Null或者為空,遵循URI Template - RFC 6570規(guī)范,則會(huì)提供一個(gè)空的值給表達(dá)式。
當(dāng)Feign解析表達(dá)式時(shí),會(huì)優(yōu)先判斷是否定義了表達(dá)式的值來(lái)決定是否要保留查詢參數(shù)名。如果未定義表達(dá)式的值,查詢參數(shù)名將會(huì)被去除,看下面的示例。

Empty String

public void test() {
   Map<String, Object> parameters = new LinkedHashMap<>();
   parameters.put("param", "");
   this.demoClient.test(parameters);
}

Result

http://localhost:8080/test?param=

Missing

public void test() {
   Map<String, Object> parameters = new LinkedHashMap<>();
   this.demoClient.test(parameters);
}

Result

http://localhost:8080/test

Undefined

public void test() {
   Map<String, Object> parameters = new LinkedHashMap<>();
   parameters.put("param", null);
   this.demoClient.test(parameters);
}

Result

http://localhost:8080/test

高級(jí)用法 提供了更多的示例.

關(guān)于斜杠/

默認(rèn)情況下@RequestLine@QueryMap 模板不會(huì)對(duì)/編碼,如果要對(duì)其編碼可以設(shè)置@RequestLinedecodeSlash值為false。

關(guān)于加號(hào) +

根據(jù)URI規(guī)范,請(qǐng)求地址和請(qǐng)求參數(shù)是允許使用加號(hào)+的,但是在查詢的處理上可能會(huì)不一致。在一些舊的系統(tǒng)上+可能會(huì)被當(dāng)作空白字符。Feign根據(jù)現(xiàn)代系統(tǒng)的做法不會(huì)將+當(dāng)作空白字符,而是編碼為%2B

如果要將+處理成空白字符,可以使用空格或者直接將其編碼為%20

自定義擴(kuò)展

@Param可以通過(guò)可選屬性expander對(duì)某一參數(shù)進(jìn)行擴(kuò)展處理。expander屬性需要接收一個(gè)實(shí)現(xiàn)了Expander接口的實(shí)現(xiàn)類。

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

方法的返回值規(guī)則與上述規(guī)則相同。忽略掉無(wú)法解析的表達(dá)式;
如果沒(méi)有對(duì)字符串或變量進(jìn)行編碼或未通過(guò)@Param注解期編碼方式,默認(rèn)會(huì)使用pct-encoded編碼。更多使用示例參考Custom @Param Expansion

請(qǐng)求頭擴(kuò)展

HeadersHeaderMap 模板規(guī)則與Request Parameter Expansion 規(guī)則相同

  • 如果返回值為null或空,則移除整個(gè)header
  • 如果未進(jìn)行pct編碼,則會(huì)進(jìn)行pct編碼

Headers 示例代碼.

請(qǐng)注意@Param 參數(shù)和參數(shù)名稱:

如果@RequestLine, @QueryMap, @BodyTemplate, 或 @Headers擁有相同的表達(dá)式名稱,那么這些表達(dá)式會(huì)被賦予相同的值。如下示例中的contentType將會(huì)被同時(shí)解析到@RequestLine@Headers的表達(dá)式中

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

在設(shè)計(jì)接口時(shí)需要額外注意。

請(qǐng)求體擴(kuò)展

Body模板的規(guī)則與Request Parameter Expansion 相同:

  • 忽略掉無(wú)法解析的表達(dá)式
  • 擴(kuò)展內(nèi)容在被傳遞到請(qǐng)求體前不會(huì)被編碼
  • 必須在header內(nèi)指定Content-Type。參考示例代碼 Body Templates。

定制化服務(wù)

Feign同時(shí)也提供了一些定制化的能力,你可以使用Feign.builder()來(lái)構(gòu)建自定義的接口。

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");
  }
}

多態(tài)

Feign支持創(chuàng)建多個(gè)api接口。通過(guò)繼承接口Target<T>(默認(rèn)實(shí)現(xiàn)為HardCodedTarget<T>)實(shí)現(xiàn),在程序運(yùn)行request前會(huì)動(dòng)態(tài)的發(fā)現(xiàn)并包裝請(qǐng)求。

例如,下面的場(chǎng)景對(duì)當(dāng)前使用的url增加CloudIdentityTarget進(jìn)行auth驗(yàn)證。

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 */
  }
}

更多示例

Feign包含了GitHubWikipedia的客戶端示例代碼,以供大家作為開發(fā)參考。特殊的示例請(qǐng)參考example daemon.


集成能力

Feign設(shè)計(jì)原則就是能夠與其它開源工具友好的工作,也歡迎將您喜歡的項(xiàng)目集成進(jìn)來(lái)。

Gson

Gson 包含了可以與JSON API共同使用的編碼器和解碼器

使用Feign.Builder添加GsonEncoderGsonDecoder

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 API共同使用的編碼器和解碼器

使用Feign.Builder添加JacksonEncoderJacksonDecoder

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 可以在標(biāo)準(zhǔn)的JVM或Android環(huán)境下解碼XML。

使用Sax對(duì)響應(yīng)數(shù)據(jù)進(jìn)行解析

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 API共同使用的編碼器和解碼器

使用Feign.Builder添加JAXBEncoderJAXBDecoder

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對(duì)標(biāo)準(zhǔn)的JAX-RS進(jìn)行了重新處理,目前基于1.1規(guī)范。

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請(qǐng)求交由OkHttp處理,OkHttp使用了SPDY協(xié)議,可以更好的對(duì)網(wǎng)絡(luò)進(jìn)行控制

Feign使用OkHttp只需要將OkHttp加入到classpath,然后配置Feign使用OkHttpClient。

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 client重寫URL解析,同時(shí)提供了智能路由和彈性能力。

集成ribbon需要將url改為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請(qǐng)求交由Java11 New HTTP/2 Client處理以實(shí)現(xiàn)HTTP/2。

若為Feign使用New HTTP/2客戶端需要JDK11,然后配置Feign使用Http2Client。

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

Hystrix

HystrixFeign使用Hystrix提供了斷路器的支持。

若要使用Hystrix,需要將Hystrix模塊加入classpath。然后使用HystrixFeign builder。

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

SOAP

SOAP包含了可以與XML API共同使用的編碼器和解碼器

該模塊通過(guò)JAXB和SOAPMessage對(duì)SOAP Body提供編碼和解碼。同時(shí)將SOAPFault解碼能力包裝到了原生的javax.xml.ws.soap.SOAPFaultException,所以使用時(shí)只需要捕獲SOAPFaultException來(lái)處理SOAPFault。

Add SOAPEncoder and/or SOAPDecoder to your Feign.Builder like so:

使用Feign.Builder來(lái)添加SOAPEncoderSOAPDecoder。

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的錯(cuò)誤響應(yīng)包含http code (4xx、5xx、…),則需要添加SOAPErrorDecoder對(duì)錯(cuò)誤進(jìn)行處理。

SLF4J

SLF4JModule直接將Feign's logging交由SLF4J處理,你可以方便的選擇logging的后端實(shí)現(xiàn)。

若要使用SLF4J,需要添加SLF4J和對(duì)應(yīng)的后端模塊到classpath內(nèi),然后使用Feign.builder配置logging。

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()可以指定Decoder對(duì)響應(yīng)數(shù)據(jù)進(jìn)行解碼。

如果接口內(nèi)某些方法反回了除Response, String, byte[]void之外的返回值,則需要配置一個(gè)自定義的 Decoder。

使用gson對(duì)json進(jìn)行解碼

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

如果你需要在解碼器解碼之前作一些前置操作,可以使用mapAndDecode
舉個(gè)例子,現(xiàn)在要求API只處理jsonp,可以在傳輸之前不對(duì)jsonp進(jìn)行包裝。

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

The simplest way to send a request body to a server is to define a POST method that has a String or byte[] parameter without any annotations on it. You will likely need to add a Content-Type header.

一個(gè)最簡(jiǎn)單的請(qǐng)求方式就是使用POST方法傳遞一個(gè)Stringbyte[]類型參數(shù)到服務(wù)端,有時(shí)可能需要在header內(nèi)指定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配置方式,這樣就可以發(fā)送一些類型安全的請(qǐng)求體。

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 模板

@Body注解用來(lái)聲明一個(gè)請(qǐng)求體模板,并配合@Param注解對(duì)請(qǐng)求參數(shù)進(jìn)行擴(kuò)展。在使用時(shí)需要指定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和單獨(dú)客戶端2個(gè)層面的headers配置。

API層面配置

如果接口內(nèi)的多個(gè)方法都需要包含相同headers配置,可以將headers配置在API接口上。

同時(shí)在API接口和接口方法上使用@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根據(jù)根據(jù)表達(dá)式的值,動(dòng)態(tài)的從@Param內(nèi)獲取對(duì)應(yīng)的內(nèi)容。

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

有時(shí)可能需要?jiǎng)討B(tài)的指定headers內(nèi)的key和value。這類場(chǎng)景可以使用HeaderMap 來(lái)動(dòng)態(tài)傳入headers的key和value。

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

動(dòng)態(tài)header

如果一個(gè)API接口會(huì)被不同的目標(biāo)端使用,同時(shí)需要指定不同的headers,或者在發(fā)送請(qǐng)求時(shí)作特殊處理??梢酝ㄟ^(guò)RequestInterceptor 或者
Target來(lái)實(shí)現(xiàn)

使用RequestInterceptor的例子可以參考Request Interceptors章節(jié)。下面是使用Target的實(shí)現(xiàn)方式。

  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));
    }
  }

上述這些實(shí)現(xiàn)是在Feign client構(gòu)建和使用時(shí),通過(guò)自定義RequestInterceptorTarget 來(lái)設(shè)置headers,這種方式可以配置在每一個(gè)Feign client上,同時(shí)也會(huì)對(duì)所有的API接口生效。這是一個(gè)很實(shí)用的功能,比如為所有client的每一個(gè)請(qǐng)求header增加auth token。這些擴(kuò)展方法與API方法調(diào)用使用同一個(gè)線程,所以在API方法運(yùn)行時(shí)可以根據(jù)上下文動(dòng)態(tài)的指定headers的內(nèi)容,比如,可以使用ThreadLocal為每個(gè)請(qǐng)求線程存儲(chǔ)不同的header內(nèi)容,對(duì)于實(shí)現(xiàn)特殊的請(qǐng)求線程鏈路追蹤是比較有用的方式。

高級(jí)用法

基礎(chǔ)api

有時(shí),某些服務(wù)的api會(huì)使用相同的約束。Feign可以使用接口單繼承的方式進(jìn)行配置。

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

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

通過(guò)繼承來(lái)擴(kuò)展基礎(chǔ)api的能力。

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

也可以通過(guò)泛型,約束API接口的操作對(duì)象類型。

@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

通過(guò)logger可以對(duì)API接口的調(diào)用日志作一些特殊處理。

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");
  }
}

攔截器

你可以使用RequestInterceptor攔截所有的請(qǐng)求,比如作為中間請(qǐng)求代理你可以設(shè)置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來(lái)實(shí)現(xiàn)一個(gè)身份驗(yàn)證攔截器。

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");
  }
}

自定義 @Param

Param注解的參數(shù)最終都是通過(guò)toString返回參數(shù)值,通過(guò)Param.Expander設(shè)置自定義擴(kuò)展,比如格式化日期。

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

動(dòng)態(tài)查詢參數(shù)

可以對(duì)Map類型參數(shù)使用QueryMap注解,Map類型參數(shù)的K-V就是查詢的參數(shù)名及參數(shù)值

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

QueryMapEncoder同樣可以通過(guò)JAVA BEAN生成查詢參數(shù)。

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

當(dāng)使用上面的方式,如果沒(méi)有自定義QueryMapEncoder,那么查詢參數(shù)名將默認(rèn)使用對(duì)象的成員變量名生成。使用JAVA BEAN生成如下參數(shù)"/find?name={name}&number={number}"(如果成員變量的值為null,則參數(shù)會(huì)被忽略)

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

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

設(shè)置自定義的QueryMapEncoder

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

當(dāng)使用@QueryMap注解對(duì)象時(shí),默認(rèn)編碼器通過(guò)反射方式查找對(duì)象屬性,并將屬性轉(zhuǎn)換成string類型。如果你希望使用getter和setter來(lái)構(gòu)建查詢字符串,你需要在Java Bean內(nèi)定義getter和setter方法,并使用BeanQueryMapEncoder

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

錯(cuò)誤處理

通過(guò)Feign.builder為Feign實(shí)例注冊(cè)一個(gè)自定義的ErrorDecoder,可以對(duì)異常的響應(yīng)作額外的處理。

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

所有HTTP code為非2xx的響應(yīng)都會(huì)觸發(fā) ErrorDecoderdecode方法,可以在該方法內(nèi)對(duì)響應(yīng)進(jìn)行處理,包裝自定義的異?;驁?zhí)行一些額外邏輯。

如果想要對(duì)失敗請(qǐng)求進(jìn)行重試,可以拋出一個(gè)RetryableException,則會(huì)使用已注冊(cè)的Retryer進(jìn)行重試.

重試

Feign默認(rèn)會(huì)自動(dòng)重試所有拋出了IOException的方法,也會(huì)自動(dòng)重試ErrorDecoder拋出的RetryableException請(qǐng)求。可以通過(guò)builder注冊(cè)自定義的Retryer來(lái)實(shí)現(xiàn)自定義的重試策略。

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

Retryer根據(jù)方法continueOrPropagate(RetryableException e)返回true 或者false來(lái)決定是否進(jìn)行重試,Retryer在每個(gè)Client創(chuàng)建時(shí)指定,可以根據(jù)不同的場(chǎng)景使用特定的重試策略。
如果重試失敗最終的拋出一個(gè)RetryException異常??梢酝ㄟ^(guò)為Feign client配置exceptionPropagationPolicy()選項(xiàng),會(huì)輸出請(qǐng)求失敗的原始異常信息。

靜態(tài)方式和默認(rèn)方法

Feign的目標(biāo)接口可以包含靜態(tài)方法或者默認(rèn)方法(jdk版本需要1.8及以上)??梢杂脕?lái)定義不包含其它邏輯的Feign client基礎(chǔ)api。比如,使用靜態(tài)方法創(chuàng)建一個(gè)普通client;默認(rèn)方法則可以被用來(lái)組裝查詢或定義默認(rèn)參數(shù)。

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");
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容