美文网首页
feign 官方文档翻译整理

feign 官方文档翻译整理

作者: 葬花逐流 | 来源:发表于2021-06-18 10:01 被阅读0次
feign.png

Ribbon
RibbonClient 重写了 Feign 对 URL 的解析逻辑,实现了智能路由、负载均衡和重试机制。
引入 feign-ribbon 包,用 Feign client 的名字代替 API 的域名/IP+Port :

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

学习教程

Hystrix
HystrixFeign 实现了断路器功能。
要使用断路器功能,要引入 feign-hystrix 包。

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

SLF4J
SLF4JModule 支持将 Feign 的日志写入 SLF4J(Logback, Log4J 等)。
要使用该功能,要引入 feign-slf4j 包和 Logback/Log4J/ 包。

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

Decoders
如果 Interface 中的 method 返回的类型不是 Response、String、byte[]、void,那么就需要自定义 Decoder 了,因为默认的 Decoder 仅支持这几种类型的解码(默认 Decoder 在 feign.codec 包下)。

解析 JSON 的 Decoder 配置示例(使用 feign-gson 扩展包):

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

在某些场景中,Decoder 对响应内容做解码之前需要对响应内容先做一些处理,Feign.Builder 提供了 mapAndDecode 函数支持这类场景。比如要从一个响应内容是 HTML 网页或者 XML 的 API 中抽取出有用的信息,组装成 JSON 格式的字符串,然后再使用 GonDecoder 将 JSON 字符串解码成业务对象类型;再比如目标 API 的响应内容是 jsonp (一种 json 使用模式)格式的,GsonDecoder 是无法直接做解码的,这时就要对 API 的响应内容先做一次处理,然后再用 GsonDecoder 对处理后的内容做解码:

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
定义一个参数类型是 String 或者 byte[] 的 method,向 POST 接口发送 request body :

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

上面的代码不安全,也不易读,更好的做法是将 method 参数声明成自定义类型,并给 Feign client 指定 Encoder:

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

@Headers
使用注解添加请求头
固定的键值对放到 Interface 或者 method 上:

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

动态键值对可以使用变量表达式:

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

某些场景下请求头中的键值对都是动态的,这时可以将 method 参数声明成 Map 类型,使用 @HeaderMap 实现动态请求头拼装:

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

给所有的 target 请求统一添加 Header

要给所有的 target 的 Request 统一添加请求头,需要自定义实现 RequestInterceptor(要保证线程安全),RequestInterceptor 会作用到 target 的所有 method 。

如果要给每一个 target 甚至是每一个的 method 定制独特的请求头,那就要自定义 Target 了,因为 RequestInterceptor 无法访问 method 的元数据。如果用前面介绍的 @Header @Param 的方式实现定制化就会有大量重复代码,甚至无法达到用户的目的。

使用 RequestInterceptor 设置请求头的样例详见 Request Interceptors 章节。

自定义 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));
    }
  }

示例中的 provider 和 requestIdProvider 都是 ThreadLocal 类型的,示例演示了如何给 Bank 这个 Interface (即 feign client)中的 method 添加认证请求头和请求标识(常用于实现调用链路跟踪)。

要给所有的 feign client 统一添加请求头,得自定义 RequestInterceptor;
只给某个 feign client 或者多个 feign client 中的 具有共性的 method 添加请求头,得自定义 Target ,在构建 feign client 时使用 Feign.builder().target(new DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider)); 给指定的 feign client 添加请求头处理逻辑。
解释:具有共性的 method 在代码中随处可见,对多个类中具有共性的 method 的统一处理的常用思路是切面编程 AOP 、拦截器等,在 feign 中,利用 Target 处理多个 feign client 中具有共性的 method 的方式也是这种思路。

高级用法
Basics API
feign 支持接口继承,公共部分可以放到父接口:

@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 提供的记录请求响应日志的简单实现:

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

Request Interceptors
RequstInterceptor 常用场景之一
给请求头添加代理记录追踪 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");
  }
}

RequstInterceptor 常用场景之二
给请求头添加认证身份,Feign 提供了 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");
  }
}

扩展@Param
被 @Param 标记的参数,得到的值是参数类型的 toString() 决定的。要改变这个默认的行为,可以给 @Param 指定一个自定义的 expander :

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

动态查询参数
用 @QueryMap 注解 Map 对象 构建 Request 参数:

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

用 @QueryMap 注解 POJO 对象 构建 Request 参数:

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

如果没有指定 QueryMapEncoder ,那么请求参数的 name 就是 POJO 对象的属性名。如果 POJO 属性值是 null ,那么该属性会被忽略。下面的 POJO 作为 method 参数的话,那么生成 URL 就是这样的 “/find?name={name}&number={number}”:

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

不自定义 QueryMapEncoder 的情况下,默认的 Encoder 会使用反射处理 POJO 的属性。如果不习惯用构造器定义 POJO,而习惯使用 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");
  }
}

Error Handling
HTTP 状态码不是 2xx 的响应都会触发 ErrorDecoder 中的 decode 方法,将异常响应包装成自定义异常是常见场景。如果 decode 方法返回的是 RetryableException ,将会触发请求重试,Retryer 负责重试。

Retry
默认情况下,只要 Feign 收到了 IOException ,就会触发重试,当 ErrorDecoder 抛出了 RetryableException 时也会触发请求重试。要改变这个默认行为,需要自定义一个 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) 方法返回 true 就触发重试,返回 false 则不触发。
重试后仍然失败则会抛出 RetryException 。可以利用 exceptionPropagationPolicy() 抛出重试失败的根源。

Metrics
Static and Default Methods
Interface 中不仅能声明 API 方法,还可以定义 static 方法和 default 方法(JDK 1.8+)。static 方法可以定义 feign client 的公共配置;default 方法可以用来组装查询对象、定义 API 方法的默认参数值。

Interface GitHub {
  // API 方法一
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  // API 方法二
  @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");
  }

  public static void main(String[] args) {
    // 是这样写,还是 .repos("Mike", null) 这样写?
    // 调用 API 方法二,该方法有 2 个参数,只传了第一参数,第 2 个参数是空值,feign 会调用 default repos 方法自动填充参数
    List<Repo> allReposOfMike = GitHub.connect().repos("Mike");
  }
}

通过 CompletableFuture支持异步请求
Feign 10.8 中新增了 AsyncFeign ,允许 Interface 中的 API 方法返回 CompletableFuture 类型的实例。

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

public class MyApp {
  public static void main(String... args) {
    GitHub github = AsyncFeign.asyncBuilder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");

    // Fetch and print a list of the contributors to this library.
    CompletableFuture<List<Contributor>> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors.get(1, TimeUnit.SECONDS)) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Feign 提供了 2 个异步 client 实现:

  • AsyncClient.Default
  • AsyncApacheHttp5Client

相关文章

  • feign 官方文档翻译整理

    Ribbon[https://gitee.com/nilera/openFeign#ribbon]RibbonCl...

  • Feign 官方文档翻译

    Feign 让编写java http客户端变简单 Fegin是一个java调用HTTP的客户端binder,其灵感...

  • Java 第三方资源收集

    mybatis官方文档翻译 mybatis-spring官方文档翻译 kafka 官方文档翻译

  • GCD官方文档翻译整理

    *Dispatch是一个抽象模型,用于通过简单但 *强大的API。 *在核心部分,dispatch提供了串行FIF...

  • SpringBootDocumentation-GettingS

    本专栏是对SpringBoot最新版本的官方文档翻译和整理,不会对着官方文档生硬翻译,只写了笔者认为值得记得,更多...

  • xpath用法

    ···lxml用法源自 lxml python 官方文档,更多内容请直接参阅官方文档,本文对其进行翻译与整理。lx...

  • Feign与Spring Cloud源码解析

    Feign简介   在Feign的官方文档上, 我们可以看到Feign最重要的一句话是:Feign makes w...

  • Spring Cloud Consul服务注册发现

    简介 文章翻译整理自Spring Cloud Consul官方文档。 Spring Cloud Consul项目通...

  • Unity项目窗口介绍

    本文只是个人对Unity官方文档的翻译,便于自己的知识整理。 unity官方文档链接 学习界面 花些时间看一遍编辑...

  • Angular : Modal(ui.bootstrap.mod

    最近发现网上对Angular的Modal讲解都比较笼统,所以参考官方文档进行一下翻译、整理,官方文档见: Moda...

网友评论

      本文标题:feign 官方文档翻译整理

      本文链接:https://www.haomeiwen.com/subject/rrlzsltx.html