Feign 使编写java http客户端变得更容易
Why Feign and not X?
Feign使用Jersey和CXF这样的工具来编写用于ReST或SOAP服务的java客户机。此外,Feign允许您在http库(如Apache HC)上编写自己的代码。Feign通过可定制的解码器和错误处理将您的代码与http API连接起来,并且只需要很少的开销。这些解码器和错误处理可以被写入任何text-based的http API。
How does Feign work?
Feign的工作原理是通过annotations处理成模板化的请求。参数在输出之前以一种简单的方式应用于这些模板。尽管Feign仅限于支持基于文本的api,但它极大地简化了系统方面,如请求的重试。此外,了解这一点后,Feign可以轻松地对转换进行单元测试
Java Version Compatibility
Feign 10.x and above are built on Java 8 and should work on Java 9, 10, and 11.
For those that need JDK 6 compatibility, please use Feign 9.x
Feature overview
这是一个map与当前的关键功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fi1B9aCQ-1586093354303)(src/docs/overview.png)]
项目路线图 Roadmap
Feign 11 and beyond
Making
API
clients easier
短期正在进行的. ⏰
-
响应缓存
:
支持api响应的缓存。允许用户定义响应在什么条件下可以进行缓存,以及应该使用什么类型的缓存机制。
支持内存缓存和外部缓存实现(EhCache、谷歌、Spring等)
-
完整的URI模板表达式支持
-
支持
level 1 through level 4
的URI模板表达式。 - 使用URI模板TCK来验证遵从性
-
支持
-
Logger
API 重构-
Refactor the
Logger
API to adhere closer to frameworks like SLF4J providing a common mental model for logging within Feign. This model will be used by Feign itself throughout and provide clearer direction on how the
Logger
will be used.
-
Refactor the
-
Retry
API 重构-
Refactor the
Retry
API to support user-supplied conditions and better control over back-off policies.
This may result in non-backward-compatible breaking changes
-
Refactor the
中期安排. ⏲
-
度量API
-
提供一个一流的度量API,用户可以利用它来洞察请求/响应的生命周期。可能提供更好的
OpenTracing
支持。
-
提供一个一流的度量API,用户可以利用它来洞察请求/响应的生命周期。可能提供更好的
-
通过
CompletableFuture
来支持异步- 允许对请求/响应生命周期进行未来的链接和执行程序管理。实现将需要非向后兼容的中断更改。但是,在考虑反应性执行之前,需要这个特性。
长期计划 ☁️
-
附加断路器支持。
-
支持额外的断路器实现,如:
Resilience4J
和Spring断路器
-
支持额外的断路器实现,如:
入门
典型的用法是这样的
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
public class MyApp {
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("Wager-Q", "wager");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}
Interface Annotations
Feign annotations define the
Contract
between the interface and how the underlying client
should work. Feign’s default contract defines the following annotations:
Feign注解定义了接口和底层客户端如何工作之间的契约(
Contract
)。Feign的默认合同定义了以下注释:
Annotation | Interface Target | Usage |
---|---|---|
|
Method |
Defines the
and
for request.
, values wrapped in curly-braces
are resolved using their corresponding
annotated parameters. 为请求定义HttpMethod和UriTemplate。表达式、用大括号括起来的值{expression}使用对应的@Param带注释的参数解析 |
|
Parameter |
Defines a template variable, whose value will be used to resolve the corresponding template
, by name. 通过名称定义一个模板变量,该变量的值将用于解析相应的模板表达式。 |
|
Method, Type |
Defines a
; a variation on a
. that uses
annotated values to resolve the corresponding
. When used on a
, the template will be applied to every request. When used on a
, the template will apply only to the annotated method. 定义了一个HeaderTemplate;UriTemplate的变体。它使用@Param注释的值来解析相应的表达式。当用于类型时,模板将应用于每个请求。在方法上使用时,模板将仅应用于带注释的方法。 |
|
Parameter |
Defines a
of name-value pairs, or POJO, to expand into a query string. 定义名称-值对(POJO)的映射,以展开成查询字符串。 |
|
Parameter |
Defines a
of name-value pairs, to expand into
定义名称-值对的映射,扩展为Http标头 |
|
Method |
Defines a
, similar to a
and
, that uses
annotated values to resolve the corresponding
. 定义一个模板,类似于UriTemplate和HeaderTemplate,它使用@Param注释的值来解析相应的表达式 |
重新 Request Line请求主机
如果需要将请求定向到不同的主机,则需要创建Feign客户机时提供的主机,或者希望为每个请求提供目标主机,请包含一个java.net.URI参数,Feign将使用该值作为请求目标。
@RequestLine("POST /repos/{owner}/{repo}/issues") void createIssue(URI host, Issue issue, @Param("owner") String owner, @Param("repo") String repo);
模板和表达式
Feign表达式表示由
URI Template – RFC 6570
定义的简单字符串表达式(级别1)。表达式使用其相应的Param注释的方法参数展开
Example
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()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
/* The owner and repository parameters will be used to expand the owner and repo expressions
* defined in the RequestLine.
*
* the resulting uri will be https://api.github.com/repos/OpenFeign/feign/contributors
*/
github.contributors("OpenFeign", "feign");
}
}
表达式必须包含在大括号{}中,并且可以包含正则表达式模式,用冒号分隔:
示例
:
owner
必须是字母
{owner:[a-zA-Z]*}
Request Parameter Expansion 请求参数扩展
RequestLine
and
QueryMap
模板遵循
URI Template – RFC 6570
的第1级模板,它指定了以下内容:
- 未解析表达式被省略。
-
所有文本和变量值都是pct编码的, if not already encoded or marked
encoded
via a
@Param
annotation。
Undefined vs. Empty Values
未定义表达式是表达式的值为显式null或不提供值的表达式。根据
URI Template – RFC 6570
,可以为表达式提供一个空值。当Feign解析一个表达式时,它首先确定是否定义了该值,如果定义了,那么查询参数将保留。如果表达式未定义,则删除查询参数。 See below
for a complete breakdown.
Empty String
public void test() {
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("param", "");
this.demoClient.test(parameters);
}
Result
http://localhost:8080/test?param=
Missing
public void test() {
Map<String, Object> parameters = new LinkedHashMap<>();
this.demoClient.test(parameters);
}
Result
http://localhost:8080/test
Undefined
public void test() {
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("param", null);
this.demoClient.test(parameters);
}
Result
http://localhost:8080/test
See
Advanced Usage
for more examples.
What about slashes?
/
@RequestLine templates do not encode slash
/
characters by default. To change this behavior, set the
decodeSlash
property on the
@RequestLine
to
false
.
What about plus?
+
Per the URI specification, a
+
sign is allowed in both the path and query segments of a URI, however, handling of
the symbol on the query can be inconsistent. In some legacy systems, the
+
is equivalent to the a space. Feign takes the approach of modern systems, where a
+
symbol should not represent a space and is explicitly encoded as
%2B
when found on a query string.If you wish to use
+
as a space, then use the literal
character or encode the value directly as
%20
Custom Expansion 自定义扩展
@Param注释有一个可选的属性扩展器,允许完全控制单个参数的扩展。扩展器属性必须引用实现扩展器接口的类:
public interface Expander {
String expand(Object value);
}
The result of this method adheres to the same rules stated above. If the result is
null
or an empty string,
the value is omitted. If the value is not pct-encoded, it will be. See
Custom @Param Expansion
for more examples.
Request Headers Expansion
header和HeaderMap模板遵循
Request Parameter Expansion
的规则,只是做了以下修改:
- 未解析表达式被省略。如果结果是空标头值,则删除整个标头。
- 不执行pct编码。
See
Headers
for examples.
A Note on
@Param
parameters and their names
:所有具有相同名称的表达式,无论它们在@RequestLine、@QueryMap、@BodyTemplate或@Headers中的位置如何,都将解析为相同的值。在下面的例子中,contentType的值,将被用来解析头和路径表达式:
public interface ContentService { @RequestLine("GET /api/documents/{contentType}") @Headers("Accept: {contentType}") String getDocumentByType(@Param("contentType") String type); }
Request Body Expansion
Body
templates follow the same rules as
Request Parameter Expansion
with the following alterations:
- 未解析表达式被省略。
- 在将扩展值放置到请求体之前,不会通过编码器传递。
-
Content-Type
header 必须指定。有关示例,请参见主体模板。
Customization 定制
Feign有几个可以定制的方面。对于简单的情况,您可以使用Feign.builder()来使用自定义组件构造API接口。例如:
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");
}
}
Multiple Interfaces
Feign可以生成多个api接口。这些被定义为
Target<T>
(default
HardCodedTarget<T>
),它允许在执行之前动态发现和修饰请求。
例如,下面的模式可能使用来自标识服务的当前url和验证令牌装饰每个请求。
public class CloudService {
public static void main(String[] args) {
CloudDNS cloudDNS = Feign.builder()
.target(new CloudIdentityTarget<CloudDNS>(user, apiKey));
}
class CloudIdentityTarget extends Target<CloudDNS> {
/* implementation of a Target */
}
}
示例
Feign includes example
GitHub
and
Wikipedia
clients. The denominator project can also be scraped for Feign in practice. Particularly, look at its
example daemon
Gson
Gson
includes an encoder and decoder you can use with a JSON API.
Add
GsonEncoder
and/or
GsonDecoder
to your
Feign.Builder
like so:
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
includes an encoder and decoder you can use with a JSON API.
Add
JacksonEncoder
and/or
JacksonDecoder
to your
Feign.Builder
like so:
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");
}
}
Sax
SaxDecoder
allows you to decode XML in a way that is compatible with normal JVM and also Android environments.
Here’s an example of how to configure Sax response parsing:
public class Example {
public static void main(String[] args) {
Api api = Feign.builder()
.decoder(SAXDecoder.builder()
.registerContentHandler(UserIdHandler.class)
.build())
.target(Api.class, "https://apihost");
}
}
JAXB
JAXB
includes an encoder and decoder you can use with an XML API.
Add
JAXBEncoder
and/or
JAXBDecoder
to your
Feign.Builder
like so:
public class Example {
public static void main(String[] args) {
Api api = Feign.builder()
.encoder(new JAXBEncoder())
.decoder(new JAXBDecoder())
.target(Api.class, "https://apihost");
}
}
JAX-RS
JAXRSContract
overrides annotation processing to instead use standard ones supplied by the JAX-RS specification. This is currently targeted at the 1.1 spec.
Here’s the example above re-written to use JAX-RS:
interface GitHub {
@GET @Path("/repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@PathParam("owner") String owner, @PathParam("repo") String repo);
}
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.contract(new JAXRSContract())
.target(GitHub.class, "https://api.github.com");
}
}
OkHttp
OkHttpClient
directs Feign’s http requests to
OkHttp
, which enables SPDY and better network control.
To use OkHttp with Feign, add the OkHttp module to your classpath. Then, configure Feign to use the OkHttpClient:
将Feign的http请求定向到OkHttp,从而支持SPDY和更好的网络控制。
要将OkHttp与Feign一起使用,请将OkHttp模块添加到类路径中。
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
overrides URL resolution of Feign’s client, adding smart routing and resiliency capabilities provided by
Ribbon
.
重写Feign客户端的URL解析,添加Ribbon提供的智能路由和弹性功能。
集成要求您传递您的ribbon客户端名称作为url的主机部分,
Integration requires you to pass your ribbon client name as the host part of the url, for example
myAppProd
.
public class Example {
public static void main(String[] args) {
MyService api = Feign.builder()
.client(RibbonClient.create())
.target(MyService.class, "https://myAppProd");
}
}
Hystrix
HystrixFeign
configures circuit breaker support provided by
Hystrix
.
To use Hystrix with Feign, add the Hystrix module to your classpath. Then use the
HystrixFeign
builder:
public class Example {
public static void main(String[] args) {
MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd");
}
}
todo
#todo
SOAP
SOAP
includes an encoder and decoder you can use with an XML API.
This module adds support for encoding and decoding SOAP Body objects via JAXB and SOAPMessage. It also provides SOAPFault decoding capabilities by wrapping them into the original
javax.xml.ws.soap.SOAPFaultException
, so that you’ll only need to catch
SOAPFaultException
in order to handle SOAPFault.
Add
SOAPEncoder
and/or
SOAPDecoder
to your
Feign.Builder
like so:
public class Example {
public static void main(String[] args) {
Api api = Feign.builder()
.encoder(new SOAPEncoder(jaxbFactory))
.decoder(new SOAPDecoder(jaxbFactory))
.errorDecoder(new SOAPErrorDecoder())
.target(MyApi.class, "http://api");
}
}
NB: you may also need to add
SOAPErrorDecoder
if SOAP Faults are returned in response with error http codes (4xx, 5xx, …)
SLF4J
SLF4JModule
allows directing Feign’s logging to
SLF4J
, allowing you to easily use a logging backend of your choice (Logback, Log4J, etc.)
To use SLF4J with Feign, add both the SLF4J module and an SLF4J binding of your choice to your classpath. Then, configure Feign to use the Slf4jLogger:
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
Feign.builder()
允许您指定其他配置,如:如何解码响应
If any methods in your interface return types besides
Response
,
String
,
byte[]
or
void
, you’ll need to configure a non-default
Decoder
.
Here’s how to configure JSON decoding (using the
feign-gson
extension):
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
}
}
If you need to pre-process the response before give it to the Decoder, you can use the
mapAndDecode
builder method.
An example use case is dealing with an API that only serves jsonp, you will maybe need to unwrap the jsonp before
send it to the Json decoder of your choice:
-
如果需要在将响应发送给解码器之前对其进行预处理,可以使用
mapAndDecode
builder方法。
用例:处理一个API,只服务于jsonp,你可能需要打开jsonp之前发送到Json解码器的选择:
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
The simplest way to send a request body to a server is to define a
POST
method that has a
String
or
byte[]
parameter without any annotations on it. You will likely need to add a
Content-Type
header.
- 将请求体发送到服务器的最简单方法是定义一个POST方法,该方法有一个String或byte[]参数,上面没有任何注释。您可能需要添加一个Content-Type头。
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\"}");
}
}
By configuring an
Encoder
, you can send a type-safe request body. Here’s an example using the
feign-gson
extension:
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
The
@Body
annotation indicates a template to expand using parameters annotated with
@Param
. You will likely need to add a
Content-Type
header.
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!
@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"); // <login "user_name"="denominator" "password"="secret"/>
client.json("denominator", "secret"); // {"user_name": "denominator", "password": "secret"}
}
}
Headers
根据用例,Feign支持将请求的设置标头作为api的一部分或作为客户端的一部分。
Set headers using apis
In cases where specific interfaces or calls should always have certain header values set, it
makes sense to define headers as part of the api.
在特定接口或调用应该总是设置某些头值的情况下,将头定义为api的一部分是有意义的。
静态标头可以使用’ @Headers ‘注释在api接口或方法上设置。
@Headers("Accept: application/json")
interface BaseApi<V> {
@Headers("Content-Type: application/json")
@RequestLine("PUT /api/{key}")
void put(@Param("key") String key, V value);
}
方法可以使用’ @Headers ‘中的变量展开为静态标头指定动态内容。
public interface Api {
@RequestLine("POST /")
@Headers("X-Ping: {token}")
void post(@Param("token") String token);
}
如果头字段键和值都是动态的,并且不能提前知道可能的键的范围,并且同一个api/客户机中的不同方法调用之间可能有所不同,那么就会出现这种情况(e.g. custom
metadata header fields such as “x-amz-meta-*” or “x-goog-meta-*”), 可以使用’ HeaderMap ‘注释映射参数,以构造一个使用映射的内容作为其头部参数的查询。
public interface Api {
@RequestLine("POST /")
void post(@HeaderMap Map<String, Object> headerMap);
}
这些方法指定header entries作为api的一部分,并且在构建Feign客户端时不需要任何自定义
Setting headers per target 为每个目标设置标头
要为目标上的每个请求方法定制headers,可以使用RequestInterceptor。requestinterceptor可以跨目标实例共享,并且应该是线程安全的。RequestInterceptors应用于目标上的所有请求方法。
如果需要对每个方法进行自定义,则需要自定义目标,因为请求拦截器没有访问当前方法元数据的权限。
有关使用RequestInterceptor设置报头的示例,请参阅
Request Interceptors
一节。
可以将Headers设置为自定义
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));
}
}
The methods are run when the api call is made on the
thread that invokes the api call, which allows the headers to be set dynamically at call time and
in a context-specific manner – for example, thread-local storage can be used to set different
header values depending on the invoking thread, which can be useful for things such as setting
thread-specific trace identifiers for requests.
这些方法依赖于在构建时在Feign客户端上设置的自定义RequestInterceptor或
Target
,并可用于在每个客户端上设置所有api调用的headers。这对于在每个客户机的所有api请求的标头中设置身份验证令牌之类的操作非常有用。
方法运行时api调用的线程上调用api调用,它允许头被设置在调用时动态和特定于上下文的方式——例如,thread-local存储根据调用线程设置不同的标头值,这对于为请求设置特定于线程的跟踪标识符非常有用。
进阶使用
Base Apis
在许多情况下,服务的api遵循相同的约定。Feign通过单继承接口支持这种模式。
参考示例:
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> { }
日志
您可以通过设置一个“log”来记录进出目标的http消息。下面是最简单的方法:
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()构造函数——它被标记为已弃用,不久将被删除。
The SLF4JLogger (see above) may also be of interest.
Request Interceptors
For example, if you are acting as an intermediary, you might want to propagate the
当您需要更改所有请求时,无论它们的目标是什么,您都需要配置一个“RequestInterceptor”。
例如,如果您充当中介,您可能希望传播
X-Forwarded-For
header.。
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
.
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");
}
}
Custom @Param Expansion
参数注释’ Param ‘展开基于他们的’ toString ‘。通过指定自定义的
Param.Expander
。用户可以控制这个行为,例如格式化日期。
public interface Api {
@RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date);
}
Dynamic Query Parameters
可以使用’ QueryMap ‘注释映射参数,以构造一个使用映射的内容作为其查询参数的查询。
public interface Api {
@RequestLine("GET /find")
V find(@QueryMap Map<String, Object> queryMap);
}
也可以使用
QueryMapEncoder
.从POJO对象生成查询参数
public interface Api {
@RequestLine("GET /find")
V find(@QueryMap CustomPojo customPojo);
}
When used in this manner, without specifying a custom
QueryMapEncoder
, the query map will be generated using member variable names as query parameter names. The following POJO will generate query params of (order of included query parameters not guaranteed, and as usual, if any value is null, it will be left out).
当以这种方式使用时,无需指定自定义’ QueryMapEncoder ‘,查询映射将使用成员变量名作为查询参数名生成。下面的POJO将生成”/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");
}
}
使用@QueryMap注释对象时,默认编码器使用反射检查提供的对象字段,将对象值展开为查询字符串。如果您希望使用getter和setter方法构建查询字符串(如Java Beans API中定义的那样),请使用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
如果您需要更多的控制来处理意外的响应,Feign实例可以通过构建器注册一个自定义的“ErrorDecoder”。
public class Example {
public static void main(String[] args) {
MyApi myApi = Feign.builder()
.errorDecoder(new MyErrorDecoder())
.target(MyApi.class, "https://api.hostname.com");
}
}
所有导致不在2xx范围内的HTTP状态的响应都将触发ErrorDecoder的decode方法,允许您处理响应、将故障包装成自定义异常或执行任何附加处理。如果希望再次重试请求,则抛出RetryableException。这将调用已注册的Retryer。
Retry 重试
默认情况下,无论HTTP方法如何,Feign都会自动重试IOExceptions,将它们视为暂时的网络相关异常,以及从ErrorDecoder抛出的任何RetryableException。要自定义此行为,请通过生成器注册一个自定义Retryer实例。
MyRetryer
public class Example {
public static void main(String[] args) {
MyApi myApi = Feign.builder()
.retryer(new MyRetryer())
.target(MyApi.class, "https://api.hostname.com");
}
}
Retryers负责通过从
continueOrPropagate(RetryableException e);
方法返回true或false
将为每个客户端执行创建一个Retryer实例,允许您在需要时维护每个请求的状态。
(RetryableException e)来确定重试是否发生;将为每个客户端执行创建一个Retryer实例,允许您在需要时维护每个请求的状态。
如果确定重试不成功,则会抛出最后一个RetryException。要抛出导致失败重试的原始原因,请使用exceptionPropagationPolicy()选项构建您的伪客户端。
Static and Default Methods
Feign的目标接口可能有静态或默认方法(如果使用Java 8+)。这些允许Feign客户端包含底层API没有明确定义的逻辑。例如,静态方法可以很容易地指定通用的客户端构建配置;默认方法可用于组合查询或定义默认参数。
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");
}
}
Async execution via
CompletableFuture
异步调用
CompletableFuture
Feign 10.8引入了一个新的生成器AsyncFeign,允许方法返回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 + ")");
}
}
}
Initial implementation include 2 async clients:
-
AsyncClient.Default
-
AsyncApacheHttp5Client