文章目录
feign 简介
-
Feign是声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API
-
Feign 在英文中是“假装,伪装”的意思,它是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。
- Feign通过处理注解,将请求模板化
- 当实际调用的时候,传入参数
- 根据参数再应用到请求上,进而转化成真正的请求
-
Feign通过可定制的解码器和错误处理将您的代码与http API连接起来,并且只需要很少的开销。
开源项目地址: https://github.com/OpenFeign/feign
使用场景
在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。大致流程如下:
入门示例
DEMO
典型的用法如下:
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 + ")");
}
}
}`
注解介绍
Feign注解定义了接口和底层客户端如何工作之间的契约(Contract
)。Feign的默认合同定义了以下注释:
Annotation | Interface Target | Usage |
---|---|---|
@RequestLine | Method | 为请求定义HttpMethod和UriTemplate。表达式、用大括号括起来的值{expression}使用对应的@Param带注释的参数解析 |
@Param | Parameter | 通过名称定义一个模板变量,该变量的值将用于解析相应的模板表达式。 |
@Headers | Method, Type | 定义了一个HeaderTemplate;UriTemplate。它使用@Param注释的值来解析相应的表达式。当用于类型时,模板将应用于每个请求。在方法上使用时,模板将仅应用于带注释的方法。 |
@QueryMap | Parameter | 定义名称-值对(POJO)的映射,以展开成查询字符串。 |
@HeaderMap | Parameter | 定义名称-值对的映射,扩展为Http标头 |
@Body | Method | 定义一个模板,类似于UriTemplate和HeaderTemplate,它使用@Param注释的值来解析相应的表达式 |
工作流程
各个模块功能分析
Headers
静态标头可以使用’ @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);
}
如果header键和值都是动态的, 可以使用’ HeaderMap '注释映射参数,以构造一个使用映射的内容作为其头部参数的查询。
public interface Api {
@RequestLine("POST /")
void post(@HeaderMap Map<String, Object> headerMap);
}
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!
@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"}
}
}
Encoders
- 将请求体发送到服务器的最简单方法是定义一个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\"}");
}
}
通过配置编码器,可以发送类型安全的请求体。下面是一个使用 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);
}
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"));
}
}
Decoders
如果接口中除了Response、String、byte[]或void之外还有其他方法返回类型,则需要配置一个非默认解码器。
下面是如何配置JSON解码:
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.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。
Request Interceptors
无论target是什么,都需要更改所有请求时,可以考虑配置一个RequestInterceptor。例如,如果您充当中介,您可能希望传播x - forwarding -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");
}
}
另一个常见的拦截器示例是身份验证,例如使用内置的 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");
}
}
Retry
默认情况下,无论HTTP方法如何,Feign都会自动重试IOExceptions,将它们视为暂时的网络相关异常,以及从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");
}
}
Retryers负责通过从continueOrPropagate(RetryableException e);
方法返回true或false
将为每个客户端执行创建一个Retryer实例,允许您在需要时维护每个请求的状态。
(RetryableException e)来确定重试是否发生;将为每个客户端执行创建一个Retryer实例,允许您在需要时维护每个请求的状态。
如果确定重试不成功,则会抛出最后一个RetryException。要抛出导致失败重试的原始原因,请使用exceptionPropagationPolicy()选项构建您的伪客户端。