成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

翻譯: Spring Cloud Feign使用文檔

andycall / 726人閱讀

摘要:轉(zhuǎn)載請(qǐng)注明出處翻譯使用文檔使用諸如和之類的工具來(lái)實(shí)現(xiàn)或服務(wù)的客戶端此外允許你在庫(kù)如之上編寫自己的代碼通過(guò)自定義解碼器和錯(cuò)誤處理可以用最小的開銷和最少的代碼將你的代碼關(guān)聯(lián)到任何基于文本的接口是通過(guò)將注解轉(zhuǎn)換成模板請(qǐng)求來(lái)實(shí)現(xiàn)它的功能的可以將請(qǐng)求

轉(zhuǎn)載請(qǐng)注明出處: 翻譯: Spring Cloud Feign使用文檔

Why Feign and not X?

Feign使用諸如JerseyCXF之類的工具來(lái)實(shí)現(xiàn)ReSTSOAP服務(wù)的java客戶端, 此外, Feign允許你在http庫(kù)(如: Apache HC)之上編寫自己的代碼. 通過(guò)自定義解碼器(decoders)和錯(cuò)誤處理(error handing), Feign可以用最小的開銷和最少的代碼將你的代碼關(guān)聯(lián)到任何基于文本的http接口(http APIS),

How does Feign work?

Feign是通過(guò)將注解(annotations)轉(zhuǎn)換成模板請(qǐng)求來(lái)實(shí)現(xiàn)它的功能的, Feign可以將請(qǐng)求參數(shù)直接應(yīng)用到這些模板上. 盡管Feign只支持基于文本的接口, 但同樣的它能顯著地簡(jiǎn)化系統(tǒng)的方方面面, 如請(qǐng)求重放等, 此外, Feign也可以使你的單元測(cè)試更加簡(jiǎn)單.

Java Version Campatibility

Feign 10.x及以上的版本是基于Java 8構(gòu)建的, 且應(yīng)該同樣支持Java 9、10、11, 如果你需要在JDK 6的版本上使用的話, 請(qǐng)使用Feign 9.x版本.

Basics

下面的代碼是適配Retrofit示例的用法:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List 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 contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}
Interface Annotations

Feign的注解定義了接口與底層http客戶端功能之間的約定, 默認(rèn)情況下各個(gè)注解的約定含義如下:

Annotation Interface Target Usage
@RequestLine 接口 定義請(qǐng)求的HttpMethodUriTemplate. 模板中可以使用大括號(hào)包圍的表達(dá)式({expression}), 表達(dá)式的值由@Param對(duì)應(yīng)參數(shù)的注解值提供.
@Param 參數(shù) 定義模板變量, 變量的值應(yīng)該由名字相對(duì)應(yīng)的表達(dá)式提供.
@Headers 方法、Type 定義HeaderTemplate; 使用@Param注解的值解析對(duì)應(yīng)的表達(dá)式. 當(dāng)該注解應(yīng)用在Type上時(shí), 該模板會(huì)被應(yīng)用到每一個(gè)請(qǐng)求上. 當(dāng)該注解應(yīng)用在方法上時(shí), 該模板僅會(huì)被應(yīng)用到對(duì)應(yīng)的方法上.
@QueryMap 參數(shù) 將鍵值對(duì)類型的Map、POJO展開成地址上的請(qǐng)求參數(shù)(query string)
@HeaderMap 參數(shù) 將鍵值對(duì)類型的Map展開成請(qǐng)求頭Http Headers.
@Body 方法 定義與UriTemplateHeaderTemplate類似的模板(Template), 該模板可以使用@Param的注解值解析對(duì)應(yīng)的表達(dá)式
Templates and Expressions

Feign支持由URI Template - RFC 6570定義的簡(jiǎn)單字符串(Level 1)表達(dá)式, 表達(dá)式的值從相關(guān)方法上對(duì)應(yīng)@Param注解提供, 示例如下:

public interface GitHub {
  
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List 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");
  }
}

表達(dá)式必須使用大括號(hào)({})包裹著, 并且支持使用冒號(hào)(:)分隔的正則表達(dá)式來(lái)限定表達(dá)式的值. 如限定上述例子的owner參數(shù)的值必須是字母: {owner:[a-zA-Z]*}.

Request Parameter Expansion

RequestLineQueryMap遵循 URI Template - RFC 6570規(guī)范對(duì)一級(jí)模板(Level 1 templates)的規(guī)定:

未被解析的值將會(huì)被忽略.

所有未編碼或者通過(guò)@Param注解標(biāo)記為已編碼(encoded)的字符和變量值都使用pct編碼(pct-encoded).

可以從Advanced Usage一節(jié)查看更多示例.

What about slashes? /

默認(rèn)情況下, @RequestLine@QueryMap模板不會(huì)對(duì)正斜杠/進(jìn)行編碼, 如果需要默認(rèn)對(duì)其進(jìn)行編碼的話, 可以將@RequestLinedecodeSlash屬性值設(shè)置為false.

What about plus? +

根據(jù)URI規(guī)范, +可以使用在URI地址和請(qǐng)求參數(shù)(query segments)這兩個(gè)部分上, 然而在請(qǐng)求參數(shù)(query)上對(duì)該符號(hào)的處理卻有可能不一致, 在一些遺留的系統(tǒng)上, +會(huì)被解析成一個(gè)空白符(space). 對(duì)此, Feign采用現(xiàn)代系統(tǒng)對(duì)+的解釋, 不會(huì)將+認(rèn)為是一個(gè)空白符(space), 并將請(qǐng)求參數(shù)上的+編碼為%2B.

如果你希望將+當(dāng)成空白符(space), 那么請(qǐng)直接使用一個(gè)空格 或者直接將其編碼為%20.

Custom Expansion

@Param注解有一個(gè)可選的參數(shù)expander可以用來(lái)控制單個(gè)參數(shù)的展開行為(expansion), 該屬性的值必須指向一個(gè)實(shí)現(xiàn)了Expander接口的類:

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

對(duì)該方法的返回值的處理與上述規(guī)則相同, 如果返回值是null或者是一個(gè)空字符串, 那么該值會(huì)被忽略. 如果返回值不是使用pct編碼(pct-encoded)的, 將會(huì)自動(dòng)轉(zhuǎn)換成pct編碼. 可以從 Custom @Param Expansion 一節(jié)查看更多示例.

Request Headers Expansion

@HeadersHeaderMap模板對(duì) Request Parameter Expansion 一節(jié)闡述的規(guī)則做以下修改, 并遵循之:

未被解析的值將會(huì)被忽略, 但如果解析到一個(gè)空的值(empty header value), 那么對(duì)應(yīng)的請(qǐng)求頭會(huì)被移除.

不會(huì)對(duì)請(qǐng)求頭使用pct編碼(pct-encoding).

可以從Headers一節(jié)查看示例.

關(guān)于@Param參數(shù)和參數(shù)名需要注意的點(diǎn)

無(wú)論是在@RequestLine@QueryMap、@BodyTemplate還是@Headers上的表達(dá)式, 只要表達(dá)式內(nèi)的變量名字相同, 那么它們的值也必然相同. 如下面的例子, contentType的值會(huì)同時(shí)被解析到請(qǐng)求頭(header)和路徑(path)上:

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

當(dāng)你在設(shè)計(jì)你的接口的一定要牢記這一點(diǎn).

Reuqest Body Expansion

Body模板對(duì) Request Parameter Expansion 一節(jié)闡述的規(guī)則做以下修改, 并遵循之:

未被解析的值將會(huì)被忽略.

展開的值在被解析到請(qǐng)求體之前不會(huì)經(jīng)過(guò)Encoder處理.

必須指定Content-Type請(qǐng)求頭, 可以從 Body Templates一節(jié)查看示例.

Customization

你可以在很多地方對(duì)Feign進(jìn)行定制. 比如, 你可以使用Feign.builder()對(duì)自定義的組件構(gòu)建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客戶以對(duì)使用Target(默認(rèn)是HardCodedTarget)定義的對(duì)象生成多個(gè)API接口, 這樣你可以在執(zhí)行前動(dòng)態(tài)發(fā)現(xiàn)服務(wù)或者對(duì)請(qǐng)求進(jìn)行裝飾.

例如, 下面的代碼可以實(shí)現(xiàn)為從身份服務(wù)中獲取當(dāng)前url授權(quán)令牌(auth token), 然后設(shè)置到每個(gè)請(qǐng)求上:

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

Feign包含了GitHub和Wikipedia的客戶端示例代碼, 在實(shí)踐中也可以參考這些項(xiàng)目, 尤其是example daemon.

Integrations

Feign在設(shè)計(jì)上就希望能夠和其他開源項(xiàng)目很好的整合到一起, 我們也很樂(lè)于將你喜歡的模塊添加進(jìn)來(lái).

Gson

Gson包含了和JSON接口相關(guān)的編碼(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接口相關(guān)的編碼(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環(huán)境兼容的方式解析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接口相關(guān)的編碼器(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規(guī)范提供的標(biāo)準(zhǔn)覆蓋了對(duì)注解的處理, 目前實(shí)現(xiàn)的是1.1版的規(guī)范, 示例如下:

interface GitHub {
  @GET @Path("/repos/{owner}/{repo}/contributors")
  List 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請(qǐng)求直接交由OkHttp處理, 后者實(shí)現(xiàn)了SPDY協(xié)議和提供了更好的網(wǎng)絡(luò)控制能力.

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會(huì)覆蓋Feign客戶端的URL解析, 以實(shí)現(xiàn)由Ribbon提供的智能路由和彈性能力.

RibbonFeign整合需要你將url中的主機(jī)名(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請(qǐng)求交給Java11 New HTTP/2 Client處理, 后者實(shí)現(xiàn)了HTTP/2協(xié)議.

要將New HTTP/2 ClientFeign整合使用, 你需要使用Java SDK 11, 并做如下配置:

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

HystrixFeign實(shí)現(xiàn)了由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接口相關(guān)的編碼器(SOAPEncoder)、解碼器(SOAPDecoder).

該模塊通過(guò)JAXB和SOAPMessage實(shí)現(xiàn)了對(duì)SOAP Body的編碼和解碼的支持, 通過(guò)將SOAPFault包裝秤javax.xml.ws.soap.SOAPFaultException實(shí)現(xiàn)了對(duì)SOAPFault解碼的功能, 因此, 對(duì)于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的響應(yīng)使用了表示錯(cuò)誤的狀態(tài)碼(4xx, 5xx, …)的話, 那么你還需要添加一個(gè)SOAPErrorDecoder.

SLF4J

SLF4JModule實(shí)現(xiàn)了將Feign的日志重定向到SLF4J, 這允許你很容易的就能使用你想用的日志后端(Logback、Log4J等).

要將SLF4JFeign整合, 你需要將SLF4J模塊和對(duì)應(yīng)的日志后端模塊放到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()允許你手動(dòng)指定額外的配置, 如配置如何對(duì)響應(yīng)進(jìn)行解析.

如果你接口定義的方法的返回值是除了ResponseStringbyte[]void之外的類型, 那么你必須配置一個(gè)非默認(rèn)的Decoder.

下面的代碼展示了如何配置使用feign-gson對(duì)JSON解碼:

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

如果你想在對(duì)響應(yīng)進(jìn)行解碼之前先對(duì)其做處理的話, 你可以使用mapAndDecode方法, 下面的代碼展示了對(duì)一個(gè)jsonp響應(yīng)的處理, 在將響應(yīng)交給JSON解碼器之前, 需要先對(duì)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

將一個(gè)請(qǐng)求體發(fā)送到服務(wù)器的最簡(jiǎn)單的辦法是定義一個(gè)POST請(qǐng)求方法, 該方法的參數(shù)類型是Stringbyte[], 且參數(shù)上不帶任何注解, 并且你可能還需要設(shè)置Content-Type請(qǐng)求頭(如果沒(méi)有的話):

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

而通過(guò)配置Encoder, 你可以發(fā)送一個(gè)類型安全的請(qǐng)求體, 下面的例子展示了使用feign-gson擴(kuò)展來(lái)實(shí)現(xiàn)編碼:

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注解的模板會(huì)使用@Param注解的值來(lái)展開模板內(nèi)部的表達(dá)式, 對(duì)于POST請(qǐng)求你可能還需要設(shè)置Content-Type請(qǐng)求頭(如果沒(méi)有的話):

interface LoginClient {

  @RequestLine("POST /")
  @Headers("Content-Type: application/xml")
  @Body("")
  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"); // 
    client.json("denominator", "secret"); // {"user_name": "denominator", "password": "secret"}
  }
}
Headers

Feign支持在api上為每個(gè)請(qǐng)求設(shè)置請(qǐng)求頭, 也支持為每個(gè)客戶端的請(qǐng)求設(shè)置請(qǐng)求頭, 你可以根據(jù)實(shí)際場(chǎng)景進(jìn)行選擇.

Set headers using apis

對(duì)于那些明確需要設(shè)置某些請(qǐng)求頭的接口的情況, 適用于將請(qǐng)求頭的定義作為接口的一部分.

靜態(tài)配置的請(qǐng)求頭可以通過(guò)在接口上使用@Headers注解設(shè)置:

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

也可以在方法上的@Headers使用變量展開動(dòng)態(tài)指定請(qǐng)求頭的內(nèi)容:

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

有時(shí)候, 對(duì)于同一個(gè)接口或客戶端的請(qǐng)求頭, 其鍵和值可能會(huì)隨著不同的方法調(diào)用而發(fā)生變化, 且不可預(yù)知(例如: 自定義元數(shù)據(jù)請(qǐng)求頭字段"x-amz-meta-"或"x-goog-meta-"), 此時(shí)可以在接口上聲明一個(gè)Map參數(shù), 并使用@HeaderMap注解將Map的內(nèi)容設(shè)置為對(duì)應(yīng)請(qǐng)求的請(qǐng)求頭:

public interface Api {
   @RequestLine("POST /")
   void post(@HeaderMap Map headerMap);
}

上述的幾個(gè)方法都可以在接口上指定請(qǐng)求的請(qǐng)求頭, 且不需要在構(gòu)造時(shí)對(duì)Feign客戶端做任何的定制.

Setting headers per target

當(dāng)同一個(gè)接口的請(qǐng)求需要針對(duì)不同的請(qǐng)求對(duì)象(endpoints)配置不同的請(qǐng)求頭, 或者需要對(duì)同一個(gè)接口的每個(gè)請(qǐng)求都定制其請(qǐng)求頭時(shí), 可以在Feign客戶端上使用RequestInterceptorTarget來(lái)設(shè)置請(qǐng)求頭.

使用RequestInterceptor設(shè)置請(qǐng)求頭的例子可以在Request Interceptor一節(jié)中查看示例.

使用Target設(shè)置請(qǐng)求頭的示例如下:

  static class DynamicAuthTokenTarget implements Target {
    public DynamicAuthTokenTarget(Class clazz,
                                  UrlAndTokenProvider provider,
                                  ThreadLocal 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));
    }
  }

上述方法的最終效果取決于你對(duì)RequestInterceptorTarget內(nèi)部的實(shí)現(xiàn), 可以通過(guò)這種方法對(duì)每個(gè)Feign客戶端的所有接口調(diào)用設(shè)置請(qǐng)求頭. 這在一些場(chǎng)景下是非常有用的, 如對(duì)每個(gè)Feign客戶端的所有請(qǐng)求設(shè)置認(rèn)證令牌authentication token. 這些方法是在接口調(diào)用者所在的線程中執(zhí)行的(譯者注: 需要注意線程安全), 因此請(qǐng)求頭的值可以是在調(diào)用時(shí)根據(jù)上下文動(dòng)態(tài)地設(shè)置. 例如, 可以根據(jù)不同的調(diào)用線程, 從ThreadLocal里讀取不同的數(shù)據(jù)設(shè)置請(qǐng)求頭.

Advanced usage Base Apis

大多數(shù)情況下服務(wù)的接口都遵循相同的約定. Feign使用單繼承的方式來(lái)實(shí)現(xiàn), 比如下面的例子:

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

  @RequestLine("GET /all")
  List all();
}

你可以通過(guò)繼承的方式來(lái)?yè)碛?b>BaseAPI的接口, 并實(shí)現(xiàn)其他特定的接口:

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

很多時(shí)候, 接口對(duì)資源的表示也是一致的, 因此, 也可以在基類的接口中使用泛型參數(shù):

@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

你可以通過(guò)為Feign客戶端設(shè)置Logger來(lái)記錄其http日志, 最簡(jiǎn)單的實(shí)現(xiàn)如下:

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客戶端對(duì)所有請(qǐng)求都做修改, 那么你可以配置RequestInterceptor來(lái)實(shí)現(xiàn). 例如, 如果你是請(qǐng)求的一個(gè)代理, 那么你可能會(huì)需要設(shè)置X-Forwarded-For請(qǐng)求頭:

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

另一個(gè)常見(jiàn)的使用攔截器的場(chǎng)景是授權(quán), 比如使用內(nèi)置的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注解的參數(shù)會(huì)用其toString()方法展開獲得參數(shù)值, 也可以通過(guò)制定一個(gè)自定義的Param.Expander來(lái)控制. 如對(duì)日期的格式化:

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

可以通過(guò)對(duì)Map類型的參數(shù)加上QueryMap注解, 將Map的內(nèi)容構(gòu)造成查詢參數(shù)(query parameters):

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

同樣的, 也可以通過(guò)使用QueryMapEncoder實(shí)現(xiàn)用POJO對(duì)象生成查詢參數(shù)(query parameter):

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

當(dāng)用這種方式時(shí), 如果沒(méi)有指定一個(gè)自定義的QueryMapEncoder, 那么查詢參數(shù)的(query parameter)內(nèi)容將根據(jù)對(duì)象的成員變量生成, 參數(shù)名對(duì)應(yīng)變量名. 下面的例子中, 根據(jù)POJO對(duì)象生成的查詢參數(shù)(query parameter)的內(nèi)容是"/find?name={name}&number={number}", 生成的查詢參數(shù)的順序是不固定的, 按照慣例, 如果POJO對(duì)象的某個(gè)變量值為null, 那么該變量會(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)用@QueryMao注解時(shí), 默認(rèn)的編碼器(encoder)會(huì)對(duì)對(duì)象的字段使用反射來(lái)將其展開成查詢參數(shù)(query string). 如果希望通過(guò)對(duì)象的getter和setter方法來(lái)展開查詢參數(shù)(query string), 請(qǐng)使用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

你可以通過(guò)在Feign實(shí)例構(gòu)造時(shí)注冊(cè)一個(gè)自定義的ErrorDecoder來(lái)實(shí)現(xiàn)對(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狀態(tài)碼不為2xx的響應(yīng)都會(huì)觸發(fā)ErrorDecoderdecode方法, 在這個(gè)方法內(nèi)你可以對(duì)這些響應(yīng)針對(duì)性地拋出異常, 或做其他額外的處理. 如果希望對(duì)請(qǐng)求進(jìn)行重試, 那么可以拋出RetryableException, 該異常會(huì)觸發(fā)Retryer.

Retry

默認(rèn)情況下, Feign會(huì)對(duì)產(chǎn)生IOException的請(qǐng)求自動(dòng)重試, 無(wú)論使用的是哪種HTTP方法, 都認(rèn)為IOExcdeption是由短暫的網(wǎng)絡(luò)問(wèn)題產(chǎn)生的. 對(duì)ErrorDecoder內(nèi)拋出的RetryableException也會(huì)進(jìn)行請(qǐng)求重試. 你也可以通在Feign實(shí)例構(gòu)造時(shí)設(shè)置自定義的Retryer來(lái)定制重試行為:

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

Retryer的實(shí)現(xiàn)需要決定一個(gè)請(qǐng)求是否應(yīng)該進(jìn)行重試, 可以通過(guò)continueOrPropagate(RetryableException e)方法的返回值(truefalse)來(lái)實(shí)現(xiàn). 每個(gè)Feign客戶端執(zhí)行時(shí)都會(huì)構(gòu)造一個(gè)Retryer實(shí)例, 這樣的話你可以維護(hù)每個(gè)請(qǐng)求的重新狀態(tài).

如果最終重試也失敗了, 那么會(huì)拋出RetryException, 如果希望拋出導(dǎo)致重試失敗的異常, 可以在構(gòu)造Feign客戶端時(shí)指定exceptionPropagationPolicy()選項(xiàng).

Static and Default Methods

使用Feign的接口可能是靜態(tài)的或默認(rèn)的方法(Java 8及以上支持), 這允許Feign客戶端包含一些不適用底層接口定義的邏輯. 例如, 使用靜態(tài)方法可以很輕易地指定通用客戶端構(gòu)造配置, 使用默認(rèn)方法可以用于組合查詢或定義默認(rèn)參數(shù):

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

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

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

  /**
   * Lists all contributors for all repos owned by a user.
   */
  default List 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)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/73455.html

相關(guān)文章

  • Spring Cloud 參考文檔(聲明式REST客戶端:Feign

    摘要:繼承支持通過(guò)單繼承接口支持樣板,這允許將通用操作分組為方便的基本接口。,記錄基本信息以及請(qǐng)求和響應(yīng)。例如,類定義參數(shù)和以下客戶端使用注解使用類 聲明式REST客戶端:Feign Feign是一個(gè)聲明式的Web服務(wù)客戶端,它使編寫Web服務(wù)客戶端變得更容易,要使用Feign,請(qǐng)創(chuàng)建一個(gè)接口并對(duì)其進(jìn)行注解,它具有可插拔的注解支持,包括Feign注解和JAX-RS注解,F(xiàn)eign還支持可插拔...

    wqj97 評(píng)論0 收藏0
  • 拜托!面試請(qǐng)不要再問(wèn)我Spring Cloud底層原理!

    摘要:不過(guò)大多數(shù)講解還停留在對(duì)功能使用的層面,其底層的很多原理,很多人可能并不知曉。每個(gè)線程池里的線程就僅僅用于請(qǐng)求那個(gè)服務(wù)。 歡迎關(guān)注微信公眾號(hào):石杉的架構(gòu)筆記(id:shishan100) 每日更新!精品技術(shù)文章準(zhǔn)時(shí)送上! 目錄 一、業(yè)務(wù)場(chǎng)景介紹 二、Spring Cloud核心組件:Eureka 三、Spring Cloud核心組件:Feign 四、Spring Cloud核心組件:R...

    wums 評(píng)論0 收藏0
  • 拜托!面試請(qǐng)不要再問(wèn)我Spring Cloud底層原理!

    摘要:不過(guò)大多數(shù)講解還停留在對(duì)功能使用的層面,其底層的很多原理,很多人可能并不知曉。每個(gè)線程池里的線程就僅僅用于請(qǐng)求那個(gè)服務(wù)。 歡迎關(guān)注微信公眾號(hào):石杉的架構(gòu)筆記(id:shishan100) 每日更新!精品技術(shù)文章準(zhǔn)時(shí)送上! 目錄 一、業(yè)務(wù)場(chǎng)景介紹 二、Spring Cloud核心組件:Eureka 三、Spring Cloud核心組件:Feign 四、Spring Cloud核心組件:R...

    wangjuntytl 評(píng)論0 收藏0
  • SpringCloud核心教程 | 第三篇:服務(wù)注冊(cè)與發(fā)現(xiàn) Eureka篇

    摘要:下一篇介紹基于的服務(wù)注冊(cè)與調(diào)用。服務(wù)提供者工程配置這里服務(wù)提供者是使用之前進(jìn)階教程第三篇整合連接池以及監(jiān)控改造而來(lái),這里一樣的部分就不再重復(fù)說(shuō)明,下面將說(shuō)明新增的部分。 Spring Cloud簡(jiǎn)介 Spring Cloud是一個(gè)基于Spring Boot實(shí)現(xiàn)的云應(yīng)用開發(fā)工具,它為基于JVM的云應(yīng)用開發(fā)中涉及的配置管理、服務(wù)發(fā)現(xiàn)、斷路器、智能路由、微代理、控制總線、全局鎖、決策競(jìng)選、分...

    scq000 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<