Spring Cloud(五):声明式 REST 客户端 Feign
Feign 是一个声明式 Web 客户端,让 Rest 服务调用更简单。要使用 Feign ,只需在编写接口并添加其注解,就可以定义好 http 请求的参数、格式、地址等信息。
Feign 会完全代理 Http 请求,只需向调用方法一样调用 feign 注解的客户端就可以完成服务请求及相关处理。
Spring Cloud 支持集成 Ribbon 和 Eureka,Feign 和他们一起使用时支持客户端负载均衡功能。Spring Cloud OpenFeign 文档。
Java 项目接口调用
Java 项目中调用接口通常会用到以下工具:
- HttpClient:HttpClient 是 Apache 下的子项目,功能丰富,易用,灵活和高效。
- Okhttp:一个处理网络请求的开源框架。Okhttp 具有更简洁的 API,高效的性能,并支持多种协议。
- Httpurlconnection:是 Java 的标准类,继承自 URLConnection,发送 get/post 请求,使用比较复杂。
- RestTemplate:是 Spring 提供的用于访问 Rest 服务的客户端,便捷高效。
Spring Cloud Feign
Spring Cloud 架构项目,在客户端(消费者)应用引入 eureka-client、ribbon、feign 依赖;在 Service 层创建接口文件,接口上使用注解 @FeignClient 指定要调用远程服务的应用名,创建接口方法,根据远程服务接口的调用规则定义此接口(类似于 Controller 层方法的注解定义请求路径、请求类型、参数类型等)
引入 Feign 依赖
1 | <dependency> |
Feign 注解使用
启用 Feign 客户端:Spring Boot 项目在入口类 Application 上添加启用 Feign 客户端的注解 @EnableFeignClients
1
2
3
4
5
6
7
8
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}使用 Feign 客户端:在 Service 层创建接口,在接口上使用 Feign 客户端注解 @FeignClient
1
2
3
4
5
6
7
8
9
10//注解属性还可使用 ${} 方式注入环境变量中配置的值
//如:@FeignClient(name = "${service.application.name.sakila-service}",path = "/service", configuration = FeignCustomConfig.class)
public interface StoreClient {
List<Store> getStores();
Store update( Long storeId, Store store);
}在 @FeignClient 注解中,name 属性是服务提供者的应用名(由 spring.application.name 属性定义),用于创建 Ribbon 负载均衡器;如果应用是 Eureka 客户端,name 解析的是 Eureka 服务注册表中的服务,如果没有使用 Eureka,则需要在配置文件中配置服务器列表。还可使用 url 属性指定 URL的绝对路径或仅指定主机名。
应用程序上下文中 Bean 的名称是接口的完全限定名,可以使用 qualifier 属性自定义 Bean 的别名。
消费者端的 Controller 层注入 @FeignClient注解的 Service 接口,在 Controller 层调用 Service 层接口。
Feign 调用简单示例
消费者应用:Controller 层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConsumerController {
//或使用 @Resource 注解
private FeignInterface feignInterface;
public String feignCall(){
String str = feignInterface.callHome();
return str;
}
}消费者应用:@FeignClient 注解 Service 层接口
1
2
3
4
5
6
public interface FeignInterface {
public String callHome();
}服务提供者:远程服务接口
1
2
3
4
5
6
7
8
9
10
11
public class HomeController {
public String home() {
InstanceInfo instanceInfo = eurekaClient.getNextServerFromEureka("sakila-service1", false);
String hostName = instanceInfo.getHostName();
int port = instanceInfo.getPort();
String appName = instanceInfo.getAppName();
return hostName + port + appName;
}
FeignClient 重名处理
若在一个服务里定义了多个 name 属性相同的 FeignClient ,直接启动是会报错。如下:
1 |
错误:**The bean ‘xxx.FeignClientSpecification’, defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
原因:有相同名的 FeignClient 客户端重复注册,即 @FeignClient 注解的 name 属性存在重复。
三种解决方案:
配置多个相同 name 的 Feign Client,使用
@FeignClient
注解的contextId
属性以避免这些配置 Bean 的名称冲突。*– 推荐这种*同一个服务的调用,写在同一个 @FeignClient 客户端中,这样 name 就不会重复。
开启 Spring Bean 允许覆盖,默认是禁止的,如下:
1
true =
Feign 配置详解
覆盖 Feign 默认配置
Spring Cloud Feign 支持的核心概念是可以指定所要调用的服务(即指定服务名:spring.application.name);一个消费者应用可能有多个 feign 客户端组件构成为一个集合,各个组件按需调用远程服务;该组件集合有一个名称,也可以使用 @FeignClient 注解的 contextId 属性自定义。
Spring Cloud 使用 FeignClientsConfiguration
按需为每个命名客户端创建一个新集合作为 ApplicationContext
,包含了一个 feign.Decoder
, 一个 feign.Encoder
, 一个 feign.Contract
,可以使用 @FeignClient
注解的 contextId 属性重写该集合的名称。
Spring Cloud 允许通过使用 @FeignClient 注解声明其它配置(在 FeignClientsConfiguration
之上) 来完全控制 Feign 客户端。如下:
1 |
|
此情况下,客户端由已经存在于 FeignClientsConfiguration 中的组件和 FooConfiguration 自定义的组件一起组成,后者将覆盖前者。
注意:
- FooConfiguration 配置类不需要用 @Configuration 注解。如果添加了 @Configuration 注解,则注意将其从任何包含此配置的 @ComponentScan 中排除,否则该配置将成为 feign.Decoder, feign.Encoder, feign.Contract 等的默认源。
可以将其放在任何 @ComponentScan 或 @SpringBootApplication 单独的非重叠的包中来排除,也可在 @ComponentScan 中指定排除。 - @FeignClient 注解的 serviceId 属性已过期,使用 name 属性。
- @FeignClient 注解指定服务,以前使用 url 属性,现在需要使用 name 属性。
name 和 url 属性还支持占位符注入环境变量中的值,如下:
1 |
|
Feign 注册的 Bean
Spring Cloud Netflix 默认为 feign 提供以下 Bean,这此 Bean 默认是在 org.springframework.cloud.openfeign.FeignClientsConfiguration 中完成注册的。
BeanType | beanName | ClassName |
---|---|---|
Decoder | feignDecoder | ResponseEntityDecoder (包装了 SpringDecoder) |
Encoder | feignEncoder | SpringEncoder |
Contract | feignContract | SpringMvcContract |
Feign.Builder | feignHystrixBuilder | HystrixFeign.builder() |
Logger | feignLogger | Slf4jLogger |
Client | feignClient | 如果 Ribbon 可用,则是 LoadBalancerFeignClient 否则使用 Feign 默认客户端 |
当 feign.okhttp.enabled 或 feign.httpclient.enabled 设置为 true 时,OkHttpClient 或 ApacheHttpClient 注册的 feiClient 将被使用。
若都引入 OkHttpClient 和 ApacheHttpClient 依赖,可以通过注册 Apache ClosableHttpClient Bean 或 OK HTTP OkHttpClient Bean 来自定义 HTTP client 。
HTTP 调用客户端的配置在 Feign 自动配置类 FeignAutoConfiguration 中实现,根据类是否存和属性配置是否启用来装配客户端的 Bean。
默认未提供的 Bean
Spring Cloud 默认没有为 Feign 提供以下类型的 Bean,但仍然会从应用上下文中查找这些类型的 Bean 来创建 Feign Client。
- Logger.Level
- Retryer
- ErrorDecoder
- Request.Options
Collection<RequestInterceptor>
- SetterFactory
Feign 自定义 Bean
还可以在 @FeignClient 注解的 configuration 属性指定的配置文件(如, FooConfiguration)中创建自定义的 Bean,允许覆盖默认注册的 Bean。
自定义 Feign 契约配置 Bean
原生 Feign 不支持 Spring MVC 注解。Spring Cloud OpenFeign 在 Feign 基础上做了扩展,可以让 Feign 支持 Spring MVC 的注解(如 @RequestMapping)来调用。
若想在 Spring Cloud 中使用原生的注解方式来定义客户端,可通过修改 Contract 这个配置,Spring Cloud 中默认是 SpringMvcContract。1
2
3
4
5
6
7
8
public class FooConfiguration {
public Contract feignContract() {
//替换默认的 SpringMvcContract
return new feign.Contract.Default();
}
}当使用默认的 Contract 后,Feign 客户端的接口方法就不能使用 Spring MVC 的注解了。
自定义 Basic 安全认证 Bean
通常调用的接口都是有权限控制的,认证信息大多通过参数传递,或通过请求头传递,如 Basic 认证方式。在 Feign 中可直接配置 Basic 认证方式。1
2
3
4
5
6
7
public class FooConfiguration {
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}也可以自定义属于自己的认证方式,其实就是自定义一个拦截器,实现 RequestInterceptor 接口,在 apply() 方法中执行请求之前的认证操作,然后往请求头中设置认证之后的信息。然后在配置类中声明自定义的认证拦截器为 Bean。
Feign 属性配置
@FeignClient 也可以合用属性配置进行配置
application.yml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract为 @FeignClient 指定默认配置
也可以将自定义的配置指定为默认配置,以在 @EnableFeignClients 注释的属性 defaultConfiguration 中指定,此处指定的默认配置将对所有 Feign 客户端有效。
如果更喜欢使用属性配置来配置所有 @FeignClient,则可以使用 feign name 为 default 来创建的配置属性。示例如下:
application.yml1
2
3
4
5
6
7feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic如果同时创建了 @Configuration bean 和 在配置文件中使用了属性配置,则属性配置优先,会覆盖 @Configuration 的值。如果想改变 @Configuration 的优先级,可以改变 feign.client.default-to-properties 的值为 false。
配置多个 Feign Client
如果想要创建具有相同name
或url
的多个 feign 客户端,以便它们指向同一服务器但每个都具有不同的自定义配置,必须使用 @FeClClient 注解的 contextId 属性以避免这些配置 Bean 的名称冲突。示例如下1
2
3
4
public interface FooClient {
//..
}1
2
3
4
public interface BarClient {
//..
}
备注:如果需要在 RequestInterceptor 中使用 ThreadLocal 绑定变量,则需要将 Hystrix 的线程隔离策略设置为 SEMAPHORE 或在 Feign 中禁用 Hystrix。如下设置:
application.yml
1 | # To disable Hystrix in Feign |
手动创建 Feign Client
某些情况下,需要自定义 Feign Client。可以使用 Feign Builder API来创建。
下面示例,创建两个具有相同接口的 Feign Client,但使用单独的请求拦截器配置每个客户端。
1 | //FeignClientsConfiguration.class 是 Spring Cloud Netflix 提供的默认配置 |
Feign Hystrix 支持
如果添加了 Hystrix 依赖包,并且 feign.hystrix.enabled=true,Feign 将所有方法包装在 断路器 中,返回一个可用的com.netflix.hystrix.HystrixCommand ,这允午使用响模式(通过调用 .toObservable() 或 .observe() 或异步使用(调用*.queue()*)。
如果要在某一个客户端的禁用 Hystrix 的支持,需要创建一个作用域是 prototype 范围的 Feign.Builder,@FeignClient 客户端 configuration 属性指定该配置类。例如:
1 |
|
备注:在 Spring Cloud Dalston 发布之前,如果Hystrix在类路径上,Feign会默认将所有方法包装在 断路器 中。 Spring Cloud Dalston 中更改了此默认行为,转而采用了选择加入方法。
Feign Hystrix 回退
Hystrix 支持回退的概念,即当断路器打开或执行出错时,会去执行一段默认的代码。
要为给定 @FeignClient 启用回退,冉要将 fallback 属性设置为实现了回退接口的类名,并将其声明为 Spring Bean。
1 |
|
如果要访问触发回退的原因,需要使用 @FeignClient 的 fallbackFactory 属性,示例如下:
1 |
|
备注: Feign 回退的实现以及 Hystrix 回退的工作方式存在局限性,对于返回 com.netflix.hystrix.hystrixcommand 和rx.observable 的方法,目前不支持回退。
Feign 和 @Primary
当 Feign 和 Hystrix 回退一起使用时,在应用上下文中会存在多个相同类型的 Bean,这将会导致 @Autowired 注解无法工具,因为没有哪一个 Bean 被确切地标记为 primary。
为了解决这个问题,Spring Cloud Netflix 默认将所有 Feign 实例标记为 Primary(即 @FeignClient 注解的 primary 属性默认为 true),这样 Spring Framework 就会知道注入了那些 Bean。但在某此情况下,这种方式并不可取,可以关闭此默认行为。
1 |
|
Feign 继承支持
Feign 可以通过单个继承接来支持样板 API(抽出为公共接口),这样允许将公共操作基本的公共接口中。
UserService.java
1 | public interface UserService { |
UserResource.java
1 |
|
UserClient.java
1 | package project.user; |
备注:通常不建议在服务器和客户端之间共享接口,这样会引入紧耦合,并且不能使用 Spring MVC(方法参数映射不会被继承)
Feign 请求/响应压缩
可以使用属性配置方式为 Feign 请求开启请求 / 响应 GZIP 压缩。
1 | true = |
Feign 请求压缩为你提供类似于 Web 服务器中的设置:
1 | true = |
通过这些属性,允许针对性选择要压缩的类型(media types)和指定最小压缩值的标准。
Feign 日志级别设置
每创建 Feign client 时会同时创建 logger。默认情况下,logger 的名称是用于创建 Feign Client 的接口的全限定类名。
Feign 日志仅响应 DEBUG 级别。即必须在配置文件中指定 Client 的日志级别为 DEBUG,自定义的 Logger.Level 才会生效。
application.yml.
1 | logging.level.project.user.UserClient: DEBUG |
可以为每个客户端配置 Logger.Level 日志级别:
- NONE:无日志(默认值)。
- BASIC:只输出请求方法和 URL 以及响应状态代码和执行时间。
- HEADERS:输出基本信息及请求和响应头。
- FULL:输出完整信息,包括请求和响应的头(headers)、体(body)、元数据(metadata)信息
如下示例,配置日志级别(Logger.Level) 为 FULL 级别,需要在 @FeignClient 注解的 configuration 属性指定该配置类。
1 |
|
Feign @QueryMap 支持
OpenFeign 原生自带的 @QueryMap 注解为将 pojo 映射为 get 参数提供支持,但缺少 value 属性,与 Spring 不兼容。为解决此问题,Spring Cloud OpenFeign 提供了相同作用的注解 @SpringQueryMap,注释 pojo 或 map 参数映射为请求参数。使用示例如下:
1 | // Params.java |
Feign 客户端使用 @SpringQueryMap 注释 Params 类参数:
1 |
|
Feign 超时设置
通过 Request.Options 可以配置连接超时时间和读取超时时间。
1 |
|
原生 Feign 的使用
如果没有用到 Spring Cloud ,但是想用 Feign 来代替之前的接口调用方式,则需要使用 Feign 原生注解。
具体参考 OpenFeign 在 GitHub 上的使用说明。
Spring Cloud(五):声明式 REST 客户端 Feign
http://blog.gxitsky.com/2019/03/25/SpringCloud-05-rest-client-feign-1/