Feign介绍

来源:互联网 发布:帝国cms表单反馈邮箱 编辑:程序博客网 时间:2024/05/18 15:50

引言

本文主要介绍Feign是什么,它能给我们带来什么帮助,并简要介绍Feign的设计风格及其各个组件。

本文主要参考Feign的项目主页:https://github.com/Netflix/feign。

Feign是什么

Feign是简化Java HTTP客户端开发的工具(java-to-httpclient-binder),它的灵感来自于Retrofit、JAXRS-2.0和WebSocket。Feign的初衷是降低统一绑定Denominator到HTTP API的复杂度,不区分是否为restful。

为什么使用Feign

开发人员使用Jersey和CXF等工具可以方便地编写java client,从而提供REST或SOAP服务;开发人员也可以基于Apache HC等http传输工具包编写自己的java http client;而Feign的关注点在于简化开发人员使用工具包的复杂度,以最少的代码编写代码从而提供java http客服端。通过定制解码器和异常处理,开发人员可以任意编写文本化的HTTP API。

Feign的工作机制

Feign通过处理注解生成request,从而实现简化HTTP API开发的目的,即开发人员可以使用注解的方式定制request api模板,在发送http request请求之前,feign通过处理注解的方式替换掉request模板中的参数,这种实现方式显得更为直接、可理解。

举个例子,github有个api,我们可以根据owner和repo两个参数,获取所有代码贡献者的贡献次数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
 
static class Contributor {
  String login;
  int contributions;
}
 
public static void main(String... args) {
  GitHub github = Feign.builder()
                       .decoder(new GsonDecoder())
                       .target(GitHub.class"<a href="https://api.github.com/" "="" style="text-decoration: none; border-radius: 0px; background: 0px center; border: 0px; bottom: auto; float: none; height: auto; left: auto; line-height: 20px; margin: 0px; outline: 0px; overflow: visible; padding: 0px; position: static; right: auto; top: auto; vertical-align: baseline; width: auto; box-sizing: content-box; min-height: inherit; color: rgb(255, 158, 123) !important;">https://api.github.com");
 
  // 获取贡献者列表,并打印其登录名以及贡献次数
  List<Contributor> contributors = github.contributors("netflix""feign");
  for (Contributor contributor : contributors) {
    System.out.println(contributor.login + " (" + contributor.contributions + ")");
  }
}

可定制化

Feign提供多个组件可供定制化,如拦截器、编码/解码器等,例如,开发人员可以使用Feign的构建器(Feign.builder())构造一个API接口,我们可以使用该构建器定制化我们所需要的组件,例如我们通过decoder()函数传入我们自己的解码器(AccountDecoder),代码如下:

interface Bank {
  @RequestLine("POST /account/{id}")
  Account getAccountInfo(@Param("id") String id);
}
// ......
Bank bank = Feign.builder().decoder(new AccountDecoder()).target(Bank.class"<a href="https://api.examplebank.com/" "="" style="text-decoration: none; border-radius: 0px; background: 0px center; border: 0px; bottom: auto; float: none; height: auto; left: auto; line-height: 20px; margin: 0px; outline: 0px; overflow: visible; padding: 0px; position: static; right: auto; top: auto; vertical-align: baseline; width: auto; box-sizing: content-box; min-height: inherit; color: rgb(255, 158, 123) !important;">https://api.examplebank.com");

多接口

Feign能够生成多个接口,这些接口组成Target<T>对象(默认为HardCodedTarget<T>),它使得request在执行之前能进行接口动态发现和request的预处理。例如,开发人员可能想要给同一个url下的每个request添加认证token等信息,因此,我们可以实现自己的Target,并加上认证token,例子如下:(其中,CloudIdentityTarget可以包括多个接口)

Feign feign = Feign.builder().build();
CloudDNS cloudDNS = feign.target(new CloudIdentityTarget<CloudDNS>(user, apiKey));

多组件支持

我们打开Feign.class的源码即可找到其所包括的各个组件,有RequestInterceptor、Logger、Contract、Client、Retryer、Encoder、Decoder、ErrorDecoder、Options以及InvocationHandlerFactory。

RequestInterceptor

在request发送之前,我们可以通过配置RequestInterceptor对request进行预处理。举个例子,若我们只是将request进行转发,我们需要对每个request更改头部信息“X-Forwarded-For”,我们可以这么做:

static class ForwardedForInterceptor implements RequestInterceptor { // 实现我们自己的RequestInterceptor
  @Override public void apply(RequestTemplate template) {
    template.header("X-Forwarded-For""origin.host.com");
  }
}
// ......
Bank bank = Feign.builder()
                 .decoder(accountDecoder)
                 .requestInterceptor(new ForwardedForInterceptor()) // 配置ForwardedForInterceptor
                 .target(Bank.class"<a href="https://api.examplebank.com/" "="" style="text-decoration: none; border-radius: 0px; background: 0px center; border: 0px; bottom: auto; float: none; height: auto; left: auto; line-height: 20px; margin: 0px; outline: 0px; overflow: visible; padding: 0px; position: static; right: auto; top: auto; vertical-align: baseline; width: auto; box-sizing: content-box; min-height: inherit; color: rgb(255, 158, 123) !important;">https://api.examplebank.com");

另一个常见的用法就是进行BA认证,我们可以用Feign内置的BasicAuthRequestInterceptor,如下:

Bank bank = Feign.builder()
                 .decoder(accountDecoder)
                 .requestInterceptor(new BasicAuthRequestInterceptor(username, password))
                 .target(Bank.class"<a href="https://api.examplebank.com/" "="" style="text-decoration: none; border-radius: 0px; background: 0px center; border: 0px; bottom: auto; float: none; height: auto; left: auto; line-height: 20px; margin: 0px; outline: 0px; overflow: visible; padding: 0px; position: static; right: auto; top: auto; vertical-align: baseline; width: auto; box-sizing: content-box; min-height: inherit; color: rgb(255, 158, 123) !important;">https://api.examplebank.com");

在Feign内部将RequestInterceptor存在ArrayList中,但是RequestInterceptor的javadoc里说明了不保证多个RequestInterceptor的执行顺序。

Logging

通过配置Logger,我们可以记录request的相关信息。在Feign中,我们可以设置Logger和LogLevel两个值,例子如下:

GitHub github = Feign.builder()
                     .decoder(new GsonDecoder())
                     .logger(new Logger.JavaLogger().appendToFile("logs/http.log"))
                     .logLevel(Logger.Level.FULL)
                     .target(GitHub.class"<a href="https://api.github.com/" "="" style="text-decoration: none; border-radius: 0px; background: 0px center; border: 0px; bottom: auto; float: none; height: auto; left: auto; line-height: 20px; margin: 0px; outline: 0px; overflow: visible; padding: 0px; position: static; right: auto; top: auto; vertical-align: baseline; width: auto; box-sizing: content-box; min-height: inherit; color: rgb(255, 158, 123) !important;">https://api.github.com");

Feign内置三个Logger,分别是JavaLogger、ErrorLogger和NoOpLogger,默认为NoOpLogger。JavaLogger内部使用jul打印request日志;ErrorLogger则简单地通过“System.err.printf”打印信息;NoOpLogger不做任何操作。Feign为我们提供四种Log Level:NONE、BASIC、HEADERS和FULL,NONE为不打印,FULL为打印headers、body和metadata,BASIC打印request method和url或response状态码和headers,HEADERS打印request或response的基本信息。

Feign还实现了Slf4jLogger,我们需要引入feign-slf4j和slf4j等相关依赖包,即可使用slf4j打印request和response日志。

Contract

顾名思义,Contract组件就是用于检查request相关的约定,在Contract内部提供了抽象类BaseContract(模板方法),它主要检查Target的相关信息及其类注解、方法注解和参数注解等,在Contract提供了默认的Contract,其实现了BaseContract。

举个例子,我们创建一个接口(例如取名为GithubAPI),即代表一个request对象,在feign内部即为Target<T>,T即为该接口类型;该接口的每个方法,即对应一个request api;而Contract的作用则是检查该Target相关内容:

  1. 检查targetType(即GithubAPI)的泛型参数;
  2. 检查targetType的接口数量以及接口深度;
  3. 检查GithubAPI的类注解,如@Headers;
  4. 检查GithubAPI的方法注解,如@RequestLine、@Body、@Headers等;
  5. 检查GithubAPI的参数注解,如@Param;

Contract除了对GithubAPI进行验证之外,最后还会将其每个方法转化为MethodMetadata对象,形成接口列表,即ArrayList<MethodMetadata>。

由此可知,Feign提供给我们的高级用法,即我们可以通过接口继承的方式实现我们的Request API对象。例如,我们可能有多个资源对象都有共同的操作API接口,代码如下:

@Headers("Accept: application/json")
interface BaseApi<V> {
 
  @RequestLine("GET /api/{key}")
  V get(@Param("key") String);
 
  @RequestLine("GET /api")
  List<V> list();
 
  @Headers("Content-Type: application/json")
  @RequestLine("PUT /api/{key}")
  void put(@Param("key") String, V value);
}
 
interface FooApi extends BaseApi<Foo> { }
 
interface BarApi extends BaseApi<Bar> { }

BaseApi提供了统一的API接口,并通过参数化使得我们可以应用到不同的资源类型,如Foo和Bar。

Feign的子工程feign-jaxrs给我们提供了JAXRSContract,,它继承自Contract.BaseContract。JAX-RS简单说就是通过注解的方式将Java POJO等封装为web资源类,而Feign的子项目JAXRSContract则是根据JAX-RS规范 V1.1进行实现,目前支持一些注解可以方便的编写restful api,详见JAXRSContract主页。

Client

Client组件应该是最重要的组件之一了,因为Feign最终发送request请求以及接收response响应,都是由Client组件完成的,它们都需要实现Client接口。除了默认的Client实现(Client.Default),Feign还通过子项目(feign-httpclient、feign-okhttp、feign-ribbon)提供多种Client实现,它们都各自集成市面上比较流行的Http Client组件,如Apache HttpClient、Okhttp、Ribbon等,且其默认的Client实现为HttpURLConnection。

 

Retryer

Feign通过Retryer组件向我们提供重试机制。对于每次Request的执行,Retryer都会调用自身的clone()拷贝一份Retryer,并且在抛出RetryableException异常时进行处理,即retryer.continueOrPropagate(e)。若Retryer认为不应该重试,则将RetryableException往外抛。

Feign提供默认的Retryer实现,其构造函数使得我们可以传入period、maxPeriod和maxAttempts三个参数进行配置,默认的period为100ms,maxPeriod为1000ms,maxAttempts为5次。

Encoder和Decoder

Encoder从名字即可知道它是在request执行前进行encode操作,而Decoder则是在收到response时进行decode操作。在Feign中,除了Encoder和Decoder内部提供的默认实现,其还有feign-gson、feign-jackson、feign-jaxb、feign-jackson-jaxb和feign-sax等子工程分别提供了其对应的Encoder和Decoder。

举个例子,我们可能需要发送一个POST请求到服务器进行身份验证,其内容为json格式的字符串,包含用户名和密码,若我们不使用Encoder,则代码为:

interface LoginClient {
  @RequestLine("POST /")
  @Headers("Content-Type: application/json")
  void login(String content);
}
client.login("{\"user_name\": \"denominator\", \"password\": \"secret\"}");

由以上代码,我们将用户名和密码格式化为json格式的字符串(当然,我们也可以通过其他工具自行格式化),然后给login方法加上@Headers,表示该POST请求为json格式的内容。若我们使用feign-gson提供的Encoder,我们可以简化我们的API,而且能确保json内容的数据类型是正确的,代码如下:

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);
}
// ......
LoginClient client = Feign.builder()
                          .encoder(new GsonEncoder())
                          .target(LoginClient.class"<a href="https://foo.com/" "="" style="text-decoration: none; border-radius: 0px; background: 0px center; border: 0px; bottom: auto; float: none; height: auto; left: auto; line-height: 20px; margin: 0px; outline: 0px; overflow: visible; padding: 0px; position: static; right: auto; top: auto; vertical-align: baseline; width: auto; box-sizing: content-box; min-height: inherit; color: rgb(255, 158, 123) !important;">https://foo.com");
 
client.login(new Credentials("denominator""secret"));

同样的,Feign提供的默认Decoder只支持Response、String、byte[]和void的response返回类型,若我们收到的response的返回类型为json等,我们需要解析为相应的对象,如上述提到过的Contributor对象。此时,我们时候用GsonDecoder可以快速达到目的,代码如下:

GitHub github = Feign.builder()
                     .decoder(new GsonDecoder())
                     .target(GitHub.class"<a href="https://api.github.com/" "="" style="text-decoration: none; border-radius: 0px; background: 0px center; border: 0px; bottom: auto; float: none; height: auto; left: auto; line-height: 20px; margin: 0px; outline: 0px; overflow: visible; padding: 0px; position: static; right: auto; top: auto; vertical-align: baseline; width: auto; box-sizing: content-box; min-height: inherit; color: rgb(255, 158, 123) !important;">https://api.github.com");

ErrorDecoder

除了上文提到了Decoder,Feign还专门提供ErrorDecoder组件,用于异常的response。在Feign执行request请求并收到response时,若response status code不是2xx,则认为出现异常,需要用ErrorDecoder进行处理。Feign提供默认的ErrorDecoder实现,即先检查response headers中的Retry-After,若存在则返回RetryableException,否则直接返回FeignException。

值得说明的是Feign单独提供了decode404字段,它是boolean值,当它为true且response status为404,则由Decoder组件处理;若它为false,则即使response status为404,都由ErrorDecoder处理。

Options

Options是用于设置Client中http request的一些参数,目前,Feign中的Options为Request.Options,包括connectTimeoutMillis和readTimeoutMillis两个字段。当然,我们若希望实现自己的http client,需要灵活配置更多的参数,我们也可以实现自己的Options,使之包括更多的参数,并在创建Feign对象是通过options(myOptions)方法替换。

目前,Request.Options的connectTimeoutMillis和readTimeoutMillis两个参数已经足够其支持的http client使用了。

InvocationHandlerFactory

 

Hystrix

Feign的子工程feign-hystrix集成了Hystrix,它实现自己的Contract、InvocationHandler和HystrixFeign,使得Feign http request具有Hystrix的功能,而且支持Fallback的降级功能。

本文不做详细叙述,要想了解,可访问项目主页:feign-hystrix和Hystrix。



0 0