转载

翻译: Spring Cloud Feign使用文档

转载请注明出处: 翻译: Spring Cloud Feign使用文档

Why Feign and not X?

Feign 使用诸如 JerseyCXF 之类的工具来实现 ReSTSOAP 服务的java客户端, 此外, Feign 允许你在http库(如: Apache HC )之上编写自己的代码. 通过自定义解码器( decoders )和错误处理( error handing ), Feign 可以用最小的开销和最少的代码将你的代码关联到任何基于文本的http接口( http APIS ),

How does Feign work?

Feign 是通过将注解( annotations )转换成模板请求来实现它的功能的, Feign 可以将请求参数直接应用到这些模板上. 尽管 Feign 只支持基于文本的接口, 但同样的它能显著地简化系统的方方面面, 如请求重放等, 此外, Feign 也可以使你的单元测试更加简单.

Java Version Campatibility

Feign 10.x 及以上的版本是基于Java 8构建的, 且应该同样支持Java 9、10、11, 如果你需要在JDK 6的版本上使用的话, 请使用 Feign 9.x 版本.

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客户端功能之间的约定, 默认情况下各个注解的约定含义如下:

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 注解提供, 示例如下:

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 )的规定:

  • 未被解析的值将会被忽略.
  • 所有未编码或者通过 @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 包含了 GitHub 和 Wikipedia 的客户端示例代码, 在实践中也可以参考这些项目, 尤其是 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");
  }
}
原文  https://segmentfault.com/a/1190000018313243
正文到此结束
Loading...