Feign用法教程
Maven依赖
创建java项目,引入maven
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |        <dependency>           <groupId>io.github.openfeign</groupId>           <artifactId>feign-core</artifactId>           <version>10.7.0</version>       </dependency>
         <dependency>           <groupId>io.github.openfeign</groupId>           <artifactId>feign-slf4j</artifactId>           <version>10.7.0</version>               </dependency>             <dependency>           <groupId>ch.qos.logback</groupId>           <artifactId>logback-classic</artifactId>       </dependency>
 
  | 
 
典型示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | public class App {
      interface GitHub {         @RequestLine("GET /repos/{owner}/{repo}/contributors")         String contributors(@Param("owner") String owner, @Param("repo") String repo);     }
      public static void main(String... args) {         GitHub github = Feign.builder()                 .logger(new Slf4jLogger())                 .logLevel(Logger.Level.FULL)                 .target(GitHub.class, "https://api.github.com");                  String contributors = github.contributors("OpenFeign", "feign");     }      }
 
  | 
 
支持的注解
| 注解 | 
target | 
说明 | 
| @RequestLine | 
方法上 | 
按照上面的示例一样,可以在此注解中表名请求method,和请求地址,并且可以使用{表达式},这样的形式来从参数中提取数据,动态构造请求地址。 | 
| @Param | 
参数上 | 
和上面示例一样,标记一个参数,这样在各种表达式中才能使用 | 
| @Headers | 
方法上 类上 | 
这个注解有一个value参数,里面放请求头,参数中和@requestLine类似,可以使用表达式来动态创建,如果标记在方法上则此方法对应的请求有效,如果标记在类上,则整个类所有方法对应的请求有效。 | 
| @QeuryMap | 
参数上 | 
将一个map或pojo标记为查询参数,取提取数据扩展成查询参数拼接在url后面 | 
| @HeaderMap | 
参数上 | 
和queryyMap同理,将键值对当成head处理 | 
| @Body | 
方法上 | 
用法和RequestLine类似,它的值会被当作请求体看待,如可以是一个json字符串,里面也可以用表达式 | 
覆盖请求host
		如果不希望Feign.builder这样生成接口代理类时指定请求的host,那么可以在方法上添加一个	java.net.URI的参数,此参数将覆盖builder生成代理类时传入的host。
1 2
   | @RequestLine("POST /repos/{owner}/{repo}/issues") void createIssue(URI host, Issue issue, @Param("owner") String owner, @Param("repo") String repo);
 
  | 
模板和表达式
	Feign 表达式遵守 这里定义的String表达式(Level 1),使用Param注解来扩展表达式
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | 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()                          .target(GitHub.class, "https://api.github.com");          
 
 
 
      github.contributors("OpenFeign", "feign");   } }
 
  | 
 
表达式必须用大括号{}包起来的形式,也可以使用正则来限制表达式解析后的值,正则使用冒号:进行分隔,比如 {owner:[a-zA-Z]*} 这样来限制owner必须是大小写字母。即前面的值必须满足后面的正则表达式, feign.template.Expressions#125
请求路径和参数扩展
RequestLine 和QueryMap模板符合 URI Template -RFC6570规范,下面需要注重指出:
Undefined vs Empty Values
Undefined的意思是 null,根据 URI Template - RFC 6570规范,可以为表达式提供一个空值。当Feign解析一个表达式时,它首先确认该值知否被定义,如果被定义了,则query parameter会被保留,如果没有被定义,则query parameter会被移除,下文会详细解释。
表达式出现在path中
如果是空字符串
  1 2 3 4 5 6 7 8 9 10 11 12 13
   | interface Fq {        @RequestLine(value = "GET /item/{id}/create")        String getItemCoupon(@Param("id") String id);    }             public static void main(String... args) {        Fq target = Feign.builder()                .logger(new Slf4jLogger())                .logLevel(Logger.Level.FULL)                .target(Fq.class, "http://localhost");        String itemCoupon = target.getItemCoupon("");        
 
 
  | 
 
 
如果是null
  1 2 3 4 5 6 7 8 9 10 11 12
   |     interface Fq {         @RequestLine(value = "GET /item/{id}/create")         String getItemCoupon(@Param("id") String id);     }
      public static void main(String... args) {         Fq target = Feign.builder()                 .logger(new Slf4jLogger())                 .logLevel(Logger.Level.FULL)                 .target(Fq.class, "http://localhost");         String itemCoupon = target.getItemCoupon(null);
 
 
  | 
 
 
如果是+或/ 这种特殊字符
  1 2 3 4 5 6 7 8 9 10 11 12 13
   |     interface Fq {        @RequestLine(value = "GET /item/{id}/create")        String getItemCoupon(@Param("id") String id);    }             public static void main(String... args) {        Fq target = Feign.builder()                .logger(new Slf4jLogger())                .logLevel(Logger.Level.FULL)                .target(Fq.class, "http://localhost");        String itemCoupon = target.getItemCoupon("a+b/c");
 
 
 
  | 
 
由上我们可以看出,如果参数表达式出现在path中,是不会被转码的,且如果是null的会变成空字符串。
 
 
表达式出现在queryString中
空字符串
  1 2 3 4 5 6 7 8 9 10 11 12
   |     interface Fq {         @RequestLine(value = "GET /item?id={id}")         String getItemCoupon(@Param("id") String id);     }
      public static void main(String... args) {         Fq target = Feign.builder()                 .logger(new Slf4jLogger())                 .logLevel(Logger.Level.FULL)                 .target(Fq.class, "http://localhost");         String itemCoupon = target.getItemCoupon("");
 
 
  | 
 
 
如果是null
  1 2 3 4 5 6 7 8 9 10 11 12 13
   |     interface Fq {         @RequestLine(value = "GET /item?id={id}")         String getItemCoupon(@Param("id") String id);     }
      public static void main(String... args) {         Fq target = Feign.builder()                 .logger(new Slf4jLogger())                 .logLevel(Logger.Level.FULL)                 .target(Fq.class, "http://localhost");         String itemCoupon = target.getItemCoupon(null);
         
 
  | 
 
 
如果是+或/ 这种特殊字符
  1 2 3 4 5 6 7 8 9 10 11 12
   |     interface Fq {         @RequestLine(value = "GET /item?id={id}")         String getItemCoupon(@Param("id") String id);     }
      public static void main(String... args) {         Fq target = Feign.builder()                 .logger(new Slf4jLogger())                 .logLevel(Logger.Level.FULL)                 .target(Fq.class, "http://localhost");         String itemCoupon = target.getItemCoupon("a+b/c");
 
 
  | 
 
由上可以看出,出现在参数中的模板表达式,如果是空字符串则参数名会被保留下来,如果是null则移除参数名,如果有特殊字符,则进行转码。
 
 
如果是@QueryMap拼接查询参数
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   |  interface Fq {         @RequestLine(value = "GET /item")         String getItemCoupon(@QueryMap Map map);     }
      public static void main(String... args) {         Fq target = Feign.builder()                 .logger(new Slf4jLogger())                 .logLevel(Logger.Level.FULL)                 .target(Fq.class, "http://localhost");         Map<String, Object> map = new HashMap<>();         map.put("a","1+1/2");         map.put("b","");         map.put("c",null);
          String itemCoupon = target.getItemCoupon(map);         
          
 
  | 
 
由上可以看出使用RequestMap 传递参数时,会对参数进行编码,且如果参数的值为null或空字符串,则保留参数的键
 
查看Advanced Usage 有更多的示例。
如何处理斜杠 /
默认情况下@RequestLine不会 encode 斜杠,要改变这种默认行为,可以将@ReqeustLine的属性decodeSlash设置为false,参考这个issus,即默认情况下:
1 2 3 4 5 6
   | @RequestMapping("/foo/{id}") String getFooById(String id) { } myFeignClient.getFooById("/1/2/3")
      
 
  | 
如何处理加号+
根据url规范,加号是允许出现在path和查询参数中的,但是现实中处理起来可能不一样,有一些老系统,会把+当成空格,但是现代的新系统在查询参数中不会将+当成空格,而是显示成%2B
如果你希望使用+作为空格,你可以直接只用 字符来当成空格,或者编码成 @2B,
例如
1 2 3 4 5
   | @RequestMapping("/foo/{id}") String getFooById(String id) { } myFeignClient.getFooById("/1+1/2")
 
 
  | 
 
上面这说的+和/ 在路径中不会转码,但在参数中会被转码
Expander接口
@Param注解有一个可选的属性 expander允许控制该参数的解析,expander属性必须指向一个Expander接口的实现类,接口如下:
1 2 3
   | public interface Expander {     String expand(Object value); }
 
  | 
 
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | interface Fq {        @RequestLine(value = "GET /item?date={date}")        String getItemCoupon(@Param(value = "date", expander = DateExpander.class) Date date);    }
          public static class DateExpander implements Param.Expander {        @Override        public String expand(Object value) {            if (!(value instanceof Date)) {                throw new RuntimeException("不支持的类型");            }            Date date = (Date) value;            return new SimpleDateFormat("yyyy-MM-dd").format(date);        }    }
     public static void main(String... args) {        Fq target = Feign.builder()                .logger(new Slf4jLogger())                .logLevel(Logger.Level.FULL)                .target(Fq.class, "http://localhost");
         String itemCoupon = target.getItemCoupon(new Date());
 
  | 
 
框架在调用此接口前判断参数是否为null,如果为null则按照上面规范忽略此参数的键值对,不再调用此接口,所以此接口的参数会被保证非null。
此接口的返回值可以是空字符串,也可以是null。返回值,按照上面[Undefined vs Empty Values](#Undefined vs Empty Values)规范处理。
返回值中的特殊字符会被pct-encoded处理,查看Custom @Param Expansion有更多示例。
请求头扩展
和请求参数扩展类似,但进行了如下修改:
- 默认不执行pct-encode ,(而请求参数扩展是默认encode的)。
 
- 未解析的表达式将被忽略,如果解析结果是空字符串,则这条请求头会被移除 (而请求参数空字符串会添加没有值的参数而不是忽略整个参数)
 
示例如下
1 2 3 4 5
   | public interface ContentService {   @RequestLine("GET /api/documents/{contentType}")   @Headers("Accept: {contentType}")   String getDocumentByType(@Param("contentType") String type); }
 
  | 
请求体扩展
@Body和请求参数扩展类似,但进行了如下修改:
- 值不会进行encoder
 
- 不能解析的表达式将被忽略,(把模板当成字符串)。
 
Content-Type头是必须要有的 
定制化
Feign允许定制,它内置了一些可以实现的接口,通过Feign.builder()来实现定制,如定制自定义decoder:
1 2 3 4 5 6 7 8 9 10 11 12
   | 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");   } }
 
  | 
 
多种接口
这个例子中,target传递的不是(class,String) 而是一个target接口的实现类,上面传递的参数(class,String)会被构造为一个HardCodedTarget (T class,String url) ,而你可以自己实现这一步。
1 2 3 4 5 6 7 8 9 10
   | public class CloudService {   public static void main(String[] args) {     CloudDNS cloudDNS = Feign.builder()       .target(new CloudIdentityTarget<CloudDNS>(user, apiKey));   }      class CloudIdentityTarget extends Target<CloudDNS> {        } }
 
  | 
 
使用自定义的target挺不错的,大家可以实现一个试试看。
整合示例
Gson
Gson包含一个编码器一个解码器,和JSON API 一起工作
1 2 3 4 5 6 7 8 9
   | 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 使用Jackson来处理JSON也很好
1 2 3 4 5 6 7 8
   | 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");   } }
 
  | 
 
OkHttp
底层使用OkHttpClient来处理发送请求,因为默认是使用java.net.URL connection来发送请求的,性能比较低
1 2 3 4 5 6 7
   | 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 提供负载均衡,需要负载均衡的可以使用这个
1 2 3 4 5 6 7
   | public class Example {   public static void main(String[] args) {     MyService api = Feign.builder()           .client(RibbonClient.create())           .target(MyService.class, "https://myAppProd");   } }
 
  | 
 
Java 11 Http2
使用java11提供的Http2,高版本jdk使用这个是很好的
1 2 3
   | GitHub github = Feign.builder()                      .client(new Http2Client())                      .target(GitHub.class, "https://api.github.com");
 
  | 
 
Hystrix
HystrixFeign使用这个让Feign使用Hystrix,带有熔断器功能
1 2 3 4 5
   | public class Example {   public static void main(String[] args) {     MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd");   } }
 
  | 
 
SLF4J
SLF4JModule 这个组件允许让Feign底层使用SLF4j来记录日志
1 2 3 4 5 6 7
   | public class Example {   public static void main(String[] args) {     GitHub github = Feign.builder()                      .logger(new Slf4jLogger())                      .target(GitHub.class, "https://api.github.com");   } }
 
  | 
 
Decoders
配置一个解码器来处理相应Response,默认的支持返回值为String,byte[],void,可以看一下源码
1 2 3 4 5 6 7
   | public class Example {   public static void main(String[] args) {     GitHub github = Feign.builder()                      .decoder(new GsonDecoder())                      .target(GitHub.class, "https://api.github.com");   } }
 
  | 
 
如果想让结果进入解码器前进行预处理,可以使用mapAndDecode方法,如下,传递解码器时传递一个lambda来预处理响应。
1 2 3 4 5 6 7
   | 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请求最简单的方法就是定义一个post请求,参数是一个不带任何注解的String或者byte[],然后不要忘了添加Content-Type请求头。
1 2 3 4 5 6 7 8 9 10 11
   | 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\"}");   } }
 
  | 
 
上面这么做很麻烦,你不得不手工处理json字符串,更简单的方法是定义一个encoder,如下编码器会将参数Credentials处理成json字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | 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标记的参数中提取数据,不要忘记添加Content-Type头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | 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")      @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");    } }
 
  | 
 
设置请求头的api
@Headers注解可以设置在类上,以表示对所有接口生效,设置在方法上表示对该方法生效。
如果类中的所有方法均要添加这个请求头,就会很有用。
1 2 3 4 5 6
   | @Headers("Accept: application/json") interface BaseApi<V> {   @Headers("Content-Type: application/json")   @RequestLine("PUT /api/{key}")   void put(@Param("key") String key, V value); }
 
  | 
 
并且@Header注解是支持动态创建请求头的,从参数中提出数据:
1 2 3 4 5
   | public interface Api {    @RequestLine("POST /")    @Headers("X-Ping: {token}")    void post(@Param("token") String token); }
 
  | 
 
如果请求头的键值都是动态的,而且还不知道具体有多少个,可以使用@HeaderMap注解处理
1 2 3 4
   | public interface Api {    @RequestLine("POST /")    void post(@HeaderMap Map<String, Object> headerMap); }
 
  | 
 
为每个target设置请求头
有时候,如果同一个接口针对不同host(注:host是可以通过注入一个java.net.URI在运行中改变的)使用不同的请求头,或者请求头是根据具体请求变化的,则可以使用RequestInterceptor 或 Target来实现。
使用RequestInterceptor的例子在RequestInterceptor章节再讲。
下面是使用Target的解决灵活的请求头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | 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));    }  }
 
  | 
 
高级用法
基础Api
在很多情况下,服务端的接口遵循一致的约定,这时可以通过继承接口来处理。
示例
1 2 3 4 5 6 7
   | interface BaseAPI {   @RequestLine("GET /health")   String health();
    @RequestLine("GET /all")   List<Entity> all(); }
 
  | 
 
你可以使用继承,来获取父类中的方法
1 2 3 4
   | interface CustomAPI extends BaseAPI {   @RequestLine("GET /custom")   String custom(); }
 
  | 
 
有些情况下,返回的Response表现形式也是一样的,如创建用户发送的json,获取用户得到的json,他们两个的形式都是一样的,可以定义公共泛型父类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | @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> { }
 
  | 
 
日志
你可以设置一个Logger 和 Logger.Level来选择用什么处理日志以及日志级别
1 2 3 4 5 6 7 8 9
   | public class Example {   public static void main(String[] args) {     GitHub github = Feign.builder()                      .decoder(new GsonDecoder())                      .logger(new Logger.JavaLogger("GitHub.Logger").appendToFile("logs/http.log"))                      .logLevel(Logger.Level.FULL)                      .target(GitHub.class, "https://api.github.com");   } }
 
  | 
 
注意:使用JavaLogger()时要避免使用无参的构造函数JavaLogger()来创建它,他有问题被弃用了,以后可能会删除。
Request Interceptors
当你需要改变所有的请求,无论这个请求的地址是什么时,可以Request Interceptors。比如你想添加X-Forwarded-For请求头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | 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
1 2 3 4 5 6 7 8
   | 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");   } }
 
  | 
 
拦截器在Target前面执行,在Target的apply前一行代码。
@Param扩展
上面讲到过@Param注解的expander 接口,下面这个例子将如果对参数进行修改,他将date格式化成毫秒值
1 2 3 4
   | public interface Api {   	@RequestLine("GET /?since={date}")      Result list(@Param(value = "date", expander = DateToMillis.class) Date date); }
 
  | 
 
注意一点,如果有两个@Param标记的参数的名字是一样的,则后面的会覆盖前面的
@QueryMap 动态查询参数
一个普通的map,添加上@QueryMap注解,它里面的值会被拿出来作为查询参数处理
1 2 3 4
   | public interface Api {   @RequestLine("GET /find")   V find(@QueryMap Map<String, Object> queryMap); }
 
  | 
 
不是map是pojo也是可以的,默认通过反射获取pojo内的字段拼接成查询参数。
也可以定义一个QueryMapEncoder来处理如何从pojo中拿值。
1 2 3 4
   | public interface Api {   @RequestLine("GET /find")   V find(@QueryMap CustomPojo customPojo); }
 
  | 
 
配置一个queryMapEncoder的例子。
1 2 3 4 5 6 7
   | public class Example {   public static void main(String[] args) {     MyApi myApi = Feign.builder()                  .queryMapEncoder(new MyCustomQueryMapEncoder())                  .target(MyApi.class, "https://api.hostname.com");   } }
 
  | 
 
默认通过反射获取值,如果希望使用java bean规范通过 get set来获取值,可以配置一个BeanQueryMapEncoder
1 2 3 4 5 6 7
   | public class Example {   public static void main(String[] args) {     MyApi myApi = Feign.builder()                  .queryMapEncoder(new BeanQueryMapEncoder())                  .target(MyApi.class, "https://api.hostname.com");   } }
 
  | 
 
注意:@QueryMap注解的解析在@Requestline之后,所以@Requestline上面定义的参数会被后来的@QueryMap定义的参数覆盖,且如果@QueryMap内定义一个空值参数会把@Requestline中定义的删掉。
Error Handling
配置一个error handDecoder,所有不再2xx范围内的响应,都会被此handler的decode方法处理。
1 2 3 4 5 6 7
   | public class Example {   public static void main(String[] args) {     MyApi myApi = Feign.builder()                  .errorDecoder(new MyErrorDecoder())                  .target(MyApi.class, "https://api.hostname.com");   } }
 
  | 
 
如果你想进行重试,可以抛出一个RetryableException,这样框架接收到了此异常调用注册的Retryer来处理重试。
Retry 重试
默认情况下会重试所有的IoException,和ErrorHandling里面抛出的RetryableException,通过在builder是设置一个Retryer来定制这种行为
1 2 3 4 5 6 7
   | 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)来决定是否重试,无返回值。
如果允许重试,则直接返回。
如果不允许重试,请将参数中的RetryableException重新抛出。
框架为每个Client执行器clone一个Retryer,所以你可以在retryer上维护状态,而不用担心冲突。
 注:为每个client创建Retryer的方式是调用Feign.build() 时传入的Retryer的clone()方法
如果不能重试成功,则抛出最后一次重试的RetryException,如果你想要导致异常的真正Exception,请使用
exceptionPropagationPolicy()构建Feign
Options配置
可以在build Feign时设置默认的Options。
如果想为每个请求设置独立Options则,接口的参数如果有类型是feign.Request.Options类型的,会作为配置传入,存在多个的情况下只取第一个。
静态和默认方法
java8+ 支持接口的静态方法和默认方法,这样就允许Feign客户端包含逻辑,
比如你想提供默认参数,或者两个接口聚合成一个一个接口返回等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | 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");   }
    
 
    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");   } }
 
  | 
 
一些内置类
feign.Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | public final class Response implements Closeable { 	   private final int status;        private final String reason;        private final Map<String, Collection<String>> headers;   	   private final Body body;        private final Request request;   .   .   .
 
  | 
 
feign.Response.Body
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | public interface Body extends Closeable { 	     Integer length(); 	     boolean isRepeatable(); 	     InputStream asInputStream() throws IOException; 	     @Deprecated     default Reader asReader() throws IOException {       return asReader(StandardCharsets.UTF_8);     } 	     Reader asReader(Charset charset) throws IOException;   }
 
  |