当前位置: 移动技术网 > IT编程>开发语言>Java > Java探索之Feign入门使用详解

Java探索之Feign入门使用详解

2019年07月19日  | 移动技术网IT编程  | 我要评论

一,简介

feign使得 java http 客户端编写更方便。feign 灵感来源于retrofit、jaxrs-2.0和websocket。feign最初是为了降低统一绑定denominator到http api的复杂度,不区分是否支持restful。feign旨在通过最少的资源和代码来实现和http api的连接。通过可定制的解码器和错误处理,可以编写任意的http api。

maven依赖:

<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-core -->
<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-core</artifactid>
  <version>8.18.0</version>
  <scope>runtime</scope>
</dependency>

二,为什么选择feign而不是其他

你可以使用 jersey 和 cxf 这些来写一个 rest 或 soap 服务的java客服端。你也可以直接使用 apache httpclient 来实现。但是 feign 的目的是尽量的减少资源和代码来实现和 http api 的连接。通过自定义的编码解码器以及错误处理,你可以编写任何基于文本的 http api。

feign工作机制

feign通过注解注入一个模板化请求进行工作。只需在发送之前关闭它,参数就可以被直接的运用到模板中。然而这也限制了feign,只支持文本形式的api,它在响应请求等方面极大的简化了系统。同时,它也是十分容易进行单元测试的。

三,feign使用简介

3.1,基本用法

基本的使用如下所示,一个对于canonical retrofit sample的适配。

interface github {
 // requestline注解声明请求方法和请求地址,可以允许有查询参数
 @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, "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 + ")");
 }
}

3.2,自定义

feign 有许多可以自定义的方面。举个简单的例子,你可以使用 feign.builder() 来构造一个拥有你自己组件的api接口。如下:

interface bank {
 @requestline("post /account/{id}")
 account getaccountinfo(@param("id") string id);
}
...
// accountdecoder() 是自己实现的一个decoder
bank bank = feign.builder().decoder(new accountdecoder()).target(bank.class, https://api.examplebank.com);

3.3,多种接口

feign可以提供多种api接口,这些接口都被定义为 target<t> (默认的实现是 hardcodedtarget<t>), 它允许在执行请求前动态发现和装饰该请求。

举个例子,下面的这个模式允许使用当前url和身份验证token来装饰每个发往身份验证中心服务的请求。
clouddns clouddns = feign.builder().target(new cloudidentitytarget<clouddns>(user, apikey));

示例

feign 包含了 github 和 wikipedia 客户端的实现样例.相似的项目也同样在实践中运用了feign。尤其是它的示例后台程序。

四,feign集成模块

feign 可以和其他的开源工具集成工作。你可以将这些开源工具集成到 feign 中来。目前已经有的一些模块如下:

4.1,gson

gson 包含了一个编码器和一个解码器,这个可以被用于json格式的api。

添加 gsonencoder 以及 gsondecoder 到你的 feign.builder 中, 如下:

gsoncodec codec = new gsoncodec();
github github = feign.builder()
           .encoder(new gsonencoder())
           .decoder(new gsondecoder())
           .target(github.class, https://api.github.com);

maven依赖:

<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-gson</artifactid>
  <version>8.18.0</version>
</dependency>

4.2,jackson

jackson 包含了一个编码器和一个解码器,这个可以被用于json格式的api。

添加 jacksonencoder 以及 jacksondecoder 到你的 feign.builder 中, 如下:

github github = feign.builder()
           .encoder(new jacksonencoder())
           .decoder(new jacksondecoder())
           .target(github.class, https://api.github.com);

maven依赖:

<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-jackson</artifactid>
  <version>8.18.0</version>
</dependency>

4.3,sax

saxdecoder 用于解析xml,并兼容普通jvm和android。下面是一个配置sax来解析响应的例子:

api = feign.builder()
.decoder(saxdecoder.builder()
.registercontenthandler(useridhandler.class)
.build())
.target(api.class, https://apihost);

maven依赖:

<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-sax</artifactid>
  <version>8.18.0</version>
</dependency>

4.4,jaxb

jaxb 包含了一个编码器和一个解码器,这个可以被用于xml格式的api。

添加 jaxbencoder 以及 jaxbdecoder 到你的 feign.builder 中, 如下:

api = feign.builder()
      .encoder(new jaxbencoder())
      .decoder(new jaxbdecoder())
      .target(api.class, https://apihost);

maven依赖:

<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-jaxb</artifactid>
  <version>8.18.0</version>
</dependency>

4.5,jax-rs

jaxrscontract 使用 jax-rs 规范重写覆盖了默认的注解处理。下面是一个使用 jax-rs 的例子:

interface github {
 @get @path("/repos/{owner}/{repo}/contributors")
 list<contributor> contributors(@pathparam("owner") string owner, @pathparam("repo") string repo);
}
// contract 方法配置注解处理器,注解处理器定义了哪些注解和值是可以作用于接口的
github github = feign.builder()
           .contract(new jaxrscontract())
           .target(github.class, https://api.github.com);

maven依赖:

<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-jaxrs</artifactid>
  <version>8.18.0</version>
</dependency>

4.5,okhttp

okhttpclient 使用 okhttp 来发送 feign 的请求,okhttp 支持 spdy (spdy是google开发的基于tcp的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验),并有更好的控制http请求。

要让 feign 使用 okhttp ,你需要将 okhttp 加入到你的环境变量中区,然后配置 feign 使用 okhttpclient,如下:

github github = feign.builder()
           .client(new okhttpclient())
           .target(github.class, "https://api.github.com");
maven依赖:
<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-okhttp</artifactid>
  <version>8.18.0</version>
</dependency>

4.6,ribbon

ribbonclient 重写了 feign 客户端的对url的处理,其添加了 智能路由以及一些其他由ribbon提供的弹性功能。
集成ribbon需要你将ribbon的客户端名称当做url的host部分来传递,如下:

// myappprod是你的ribbon client name
myservice api = feign.builder().client(ribbonclient.create()).target(myservice.class, "https://myappprod");
maven依赖:
<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-ribbon</artifactid>
  <version>8.18.0</version>
</dependency>

4.7,hystrix

hystrixfeign 配置了 hystrix 提供的熔断机制。

要在 feign 中使用 hystrix ,你需要添加hystrix模块到你的环境变量,然后使用 hystrixfeign 来构造你的api:

myservice api = hystrixfeign.builder().target(myservice.class, "https://myappprod");
maven依赖:
<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-hystrix</artifactid>
  <version>8.18.0</version>
</dependency>

4.8,slf4j

slf4jmodule 允许你使用 slf4j 作为 feign 的日志记录模块,这样你就可以轻松的使用 logback, log4j , 等 来记录你的日志.

要在 feign 中使用 slf4j ,你需要添加slf4j模块和对应的日志记录实现模块(比如log4j)到你的环境变量,然后配置 feign使用slf4jlogger :

github github = feign.builder()
           .logger(new slf4jlogger())
           .target(github.class, "https://api.github.com");
maven依赖:
<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
  <groupid>com.netflix.feign</groupid>
  <artifactid>feign-slf4j</artifactid>
  <version>8.18.0</version>
</dependency>

五,feign 组成

5.1,decoders

feign.builder() 允许你自定义一些额外的配置,比如说如何解码一个响应。假如有接口方法返回的消息不是 response, string, byte[] 或者 void 类型的,那么你需要配置一个非默认的解码器。

下面是一个配置使用json解码器(使用的是feign-gson扩展)的例子:

github github = feign.builder()
           .decoder(new gsondecoder())
           .target(github.class, https://api.github.com);

假如你想在将响应传递给解码器处理前做一些额外的处理,那么你可以使用mapanddecode方法。一个用例就是使用jsonp服务的时候:

// 貌似1.8.0版本中沒有mapanddecode这个方法。。。
jsonpapi jsonpapi = feign.builder()
             .mapanddecode((response, type) -> jsopunwrap(response, type), new gsondecoder())
             .target(jsonpapi.class, https://some-jsonp-api.com);

5.2,encoders

发送一个post请求最简单的方法就是传递一个 string 或者 byte[] 类型的参数了。你也许还需添加一个content-type请求头,如下:

interface loginclient {
 @requestline("post /")
 @headers("content-type: application/json")
 void login(string content);
}
...
client.login("{\"user_name\": \"denominator\", \"password\": \"secret\"}");

通过配置一个解码器,你可以发送一个安全类型的请求体,如下是一个使用 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);
}
...
loginclient client = feign.builder()
             .encoder(new gsonencoder())
             .target(loginclient.class, "https://foo.com");
client.login(new credentials("denominator", "secret"));

5.3,@body templates

@body注解申明一个请求体模板,模板中可以带有参数,与方法中 @param 注解申明的参数相匹配,使用方法如下:

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!
 // 这里json格式需要的花括号居然需要转码,有点蛋疼了。
 @body("%7b\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7d")
 void json(@param("user_name") string user, @param("password") string password);
}
...
client.xml("denominator", "secret"); // <login "user_name"="denominator" "password"="secret"/>
client.json("denominator", "secret"); // {"user_name": "denominator", "password": "secret"}

5.4,headers

feign 支持给请求的api设置或者请求的客户端设置请求头,如下:

给api设置请求头

使用 @headers 设置静态请求头

// 给baseapi中的所有方法设置accept请求头
@headers("accept: application/json")
interface baseapi<v> {
 // 单独给put方法设置content-type请求头
 @headers("content-type: application/json")
 @requestline("put /api/{key}")
 void put(@param("key") string, v value);
}

设置动态值的请求头

@requestline("post /")
@headers("x-ping: {token}")
void post(@param("token") string token);

设置key和value都是动态的请求头

有些api需要根据调用时动态确定使用不同的请求头(e.g. custom metadata header fields such as “x-amz-meta-“ or “x-goog-meta-“),

这时候可以使用 @headermap 注解,如下:

// @headermap 注解设置的请求头优先于其他方式设置的
@requestline("post /")
void post(@headermap map<string, object> headermap);

给target设置请求头

有时我们需要在一个api实现中根据不同的endpoint来传入不同的header,这个时候我们可以使用自定义的requestinterceptor 或 target来实现.

通过自定义的 requestinterceptor 来实现请查看 request interceptors 章节.

下面是一个通过自定义target来实现给每个target设置安全校验信息header的例子:

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();
 }
}
...
bank bank = feign.builder()
    .target(new dynamicauthtokentarget(bank.class, provider, requestidprovider));

这种方法的实现依赖于给feign 客户端设置的自定义的requestinterceptor 或 target。可以被用来给一个客户端的所有api请求设置请求头。比如说可是被用来在header中设置身份校验信息。这些方法是在线程执行api请求的时候才会执行,所以是允许在运行时根据上下文来动态设置header的。

比如说可以根据线程本地存储(thread-local storage)来为不同的线程设置不同的请求头。

六,高级用法

6.1,base apis

有些请求中的一些方法是通用的,但是可能会有不同的参数类型或者返回类型,这个时候可以这么用:

// 通用api
interface baseapi {
 @requestline("get /health")
 string health();
 @requestline("get /all")
 list<entity> all();
}
// 继承通用api
interface customapi extends baseapi {
 @requestline("get /custom")
 string custom();
}
// 各种类型有相同的表现形式,定义一个统一的api
@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> { }

6.2,logging

你可以通过设置一个 logger 来记录http消息,如下:

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

也可以参考上面的 slf4j 章节的说明

6.3,request interceptors

当你希望修改所有的的请求的时候,你可以使用request interceptors。比如说,你作为一个中介,你可能需要为每个请求设置 x-forwarded-for

static class forwardedforinterceptor implements requestinterceptor {
 @override public void apply(requesttemplate template) {
  template.header("x-forwarded-for", "origin.host.com");
 }
}
...
bank bank = feign.builder()
         .decoder(accountdecoder)
         .requestinterceptor(new forwardedforinterceptor())
         .target(bank.class, https://api.examplebank.com);

或者,你可能需要实现basic auth,这里有一个内置的基础校验拦截器

basicauthrequestinterceptor
bank bank = feign.builder()
         .decoder(accountdecoder)
         .requestinterceptor(new basicauthrequestinterceptor(username, password))
         .target(bank.class, https://api.examplebank.com);

6.4,custom @param expansion

在使用 @param 注解给模板中的参数设值的时候,默认的是使用的对象的 tostring() 方法的值,通过声明 自定义的param.expander,用户可以控制其行为,比如说格式化 date 类型的值:

// 通过设置 @param 的 expander 为 datetomillis.class 可以定义date类型的值
@requestline("get /?since={date}")
result list(@param(value = "date", expander = datetomillis.class) date date);

6.5,dynamic query parameters

动态查询参数支持,通过使用 @querymap 可以允许动态传入请求参数,如下:

@requestline("get /find")
v find(@querymap map<string, object> querymap);

6.6,static and default methods

如果你使用的是jdk 1.8+ 的话,那么你可以给接口设置统一的默认方法和静态方法,这个事jdk8的新特性,如下:

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

总结

以上就是本文关于java探索之feign入门使用详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:java编程几个循环实例代码分享java面向对象编程(封装/继承/多态)实例解析等,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网