Java探索之Feign入门使用详解
一,简介
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面向对象编程(封装/继承/多态)实例解析等,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!