On this page
17. 声明 REST Client:Feign
假装是一个声明性的 web service client。这使得编写 web service clients 更容易。要使用 Feign,请创建一个界面并对其进行注释。它具有可插入的 annotation 支持,包括 Feign annotations 和 JAX-RS _notnotations。 Feign 还支持可插拔编码器和解码器。 Spring Cloud 添加了对 Spring MVC annotations 的支持,并使用了 Spring Web 中默认使用的相同HttpMessageConverters
。使用 Feign 时,Spring Cloud 集成 Ribbon 和 Eureka 以提供负载均衡的 http client。
17.1 如何包含 Feign
要在项目中包含 Feign,请使用带有 group org.springframework.cloud
和 artifact id spring-cloud-starter-openfeign
的 starter。有关使用当前 Spring Cloud Release Train 设置 build 系统的详细信息,请参阅Spring Cloud 项目页面。
Example spring boot app
@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
StoreClient.java.
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
}
在@FeignClient
annotation 中,String value(上面的“stores”)是一个任意 client name,用于创建 Ribbon 负载均衡器(请参阅以下是 Ribbon 支持的详细信息)。您还可以使用url
属性指定 URL(绝对 value 或仅指定主机名)。 application context 中 bean 的 name 是接口的完全限定 name。要指定自己的别名 value,可以使用@FeignClient
annotation 的qualifier
value。
上面的 Ribbon client 将要发现“stores”服务的物理地址。如果您的 application 是 Eureka client,那么它将解析 Eureka 服务注册表中的服务。如果您不想使用 Eureka,只需在外部 configuration 中配置服务器列表(请参阅以上为 example)。
17.2 覆盖 Feign 默认值
Spring Cloud 的 Feign 支持的核心概念是命名的 client。每个 feign client 都是一个组件集合的一部分,它们一起工作以按需联系 remote 服务器,并且整体有一个 name,您可以使用@FeignClient
annotation 将其作为 application 开发人员提供。 Spring Cloud 使用FeignClientsConfiguration
为每个命名的 client 创建一个新的集合作为ApplicationContext
。这包含(除其他外)feign.Decoder
,feign.Encoder
和feign.Contract
。
Spring Cloud 允许您通过使用@FeignClient
声明其他 configuration(在FeignClientsConfiguration
之上)来完全控制 feign client。 例:
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
在这种情况下,client 由FeignClientsConfiguration
中的组件和FooConfiguration
中的任何组件组成(后者将覆盖前者)。
FooConfiguration
不需要用@Configuration
注释。但是,如果是,则注意将其从任何包含此 configuration 的@ComponentScan
中排除,因为它将在指定时成为feign.Decoder
,feign.Encoder
,feign.Contract
等的默认源。这可以通过将其放在任何@ComponentScan
或@SpringBootApplication
的单独的 non-overlapping 包中来避免,或者可以在@ComponentScan
中明确排除。
现在不推荐使用
serviceId
属性,而是使用name
属性。
以前,使用
url
属性不需要name
属性。现在需要使用name
。
name
和url
属性支持占位符。
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
Spring Cloud Netflix 默认为 feign(BeanType
beanName:ClassName
)提供以下 beans:
Decoder
feignDecoder:ResponseEntityDecoder
(包装SpringDecoder
)Encoder
feignEncoder:SpringEncoder
Logger
feignLogger:Slf4jLogger
Contract
feignContract:SpringMvcContract
Feign.Builder
feignBuilder:HystrixFeign.Builder
Client
feignClient:如果启用了 Ribbon,则为LoadBalancerFeignClient
,否则使用默认的 feign client。
可以通过分别将feign.okhttp.enabled
或feign.httpclient.enabled
设置为true
并将它们放在 classpath 上来使用 OkHttpClient 和 ApacheHttpClient feign clients。您可以通过使用 Apache 提供或使用 OK HTTP OkHttpClient
whe 来自定义 HTTP client。
Spring Cloud Netflix 默认情况下不为 feign 提供以下 beans,但仍然会从 application context 中查找这些类型的 beans 以创建 feign client:
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection<RequestInterceptor>
SetterFactory
创建其中一种类型的 bean 并将其置于@FeignClient
configuration(例如上面的FooConfiguration
)中,可以覆盖所描述的每个 beans。 例:
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
这将SpringMvcContract
替换为feign.Contract.Default
并将RequestInterceptor
添加到RequestInterceptor
的集合中。
也可以使用 configuration properties 配置@FeignClient
。
application.yml
feign:
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
可以以与上述类似的方式在@EnableFeignClients
属性defaultConfiguration
中指定默认配置。不同之处在于此 configuration 将适用于所有 feign clients。
如果您更喜欢使用 configuration properties 配置所有@FeignClient
,则可以使用default
feign name 创建 configuration properties。
application.yml
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
如果我们同时创建@Configuration
bean 和 configuration properties,configuration properties 将获胜。它将覆盖@Configuration
值。但是如果要将优先级更改为@Configuration
,则可以将feign.client.default-to-properties
更改为false
。
如果需要在
RequestInterceptor
中使用ThreadLocal
绑定变量,则需要将 Hystrix 的线程隔离策略设置为SEMAPHORE
或在 Feign 中禁用 Hystrix。
application.yml
# To disable Hystrix in Feign
feign:
hystrix:
enabled: false
# To set thread isolation to SEMAPHORE
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
17.3 手动创建 Feign Clients
在某些情况下,可能需要以使用上述方法无法实现的方式自定义 Feign Clients。在这种情况下,您可以使用Feign Builder API创建 Clients。下面是一个 example,它创建了两个具有相同接口的 Feign Clients,但每个都配置了一个单独的请求拦截器。
@Import(FeignClientsConfiguration.class)
class FooController {
private FooClient fooClient;
private FooClient adminClient;
@Autowired
public FooController(
Decoder decoder, Encoder encoder, Client client) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "http://PROD-SVC");
this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "http://PROD-SVC");
}
}
在上面的例子中
FeignClientsConfiguration.class
是 Spring Cloud Netflix 提供的默认 configuration。
PROD-SVC
是 Clients 将向其发出请求的服务的 name。
17.4 Feign Hystrix 支持
如果 Hystrix 位于 classpath 和feign.hystrix.enabled=true
上, Feign 将使用断路器包装所有方法。也可以返回com.netflix.hystrix.HystrixCommand
。这允许您使用 reactive 模式(通过调用.toObservable()
或.observe()
或异步使用(通过调用.queue()
)。
要在 per-client 基础上禁用 Hystrix 支持,请使用“prototype”范围创建一个带有 e.g 的 vanilla Feign.Builder
。:
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
在 Spring Cloud Dalston 发布之前,如果 Hystrix 在 classpath 上_Fign 将默认包装断路器中的所有方法。在 Spring Cloud Dalston 中更改了此默认行为,支持 opt-in 方法。
17.5 Feign Hystrix 后退
Hystrix 支持回退的概念:当电路打开或出现错误时执行的默认 code 路径。要为给定的@FeignClient
启用回退,请将fallback
属性设置为实现回退的 class name。您还需要将 implementation 声明为 Spring bean。
@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
protected interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
static class HystrixClientFallback implements HystrixClient {
@Override
public Hello iFailSometimes() {
return new Hello("fallback");
}
}
如果需要访问导致回退触发器的原因,可以使用@FeignClient
中的fallbackFactory
属性。
@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
@Override
public HystrixClient create(Throwable cause) {
return new HystrixClient() {
@Override
public Hello iFailSometimes() {
return new Hello("fallback; reason was: " + cause.getMessage());
}
};
}
}
Feign 中_backbacks 的实现以及 Hystrix 回退的工作方式存在限制。 return
com.netflix.hystrix.HystrixCommand
和rx.Observable
的方法目前不支持回退。
17.6 Feign 和 @Primary
将 Feign 与 Hystrix 回退一起使用时,同一类型的ApplicationContext
中有多个 beans。这将导致@Autowired
不起作用,因为没有一个 bean 或一个标记为主要的 bean。要解决此问题,Spring Cloud Netflix 将所有 Feign 实例标记为@Primary
,因此 Spring Framework 将知道 inject 为 inject。在某些情况下,这可能并不理想。要关闭此行为,请将@FeignClient
的primary
属性设置为 false。
@FeignClient(name = "hello", primary = false)
public interface HelloClient {
// methods here
}
17.7 Feign 继承支持
Feign 通过 single-inheritance 接口支持样板 api。这允许将 common 操作分组到方便的基接口。
UserService.java.
public interface UserService {
@RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
User getUser(@PathVariable("id") long id);
}
UserResource.java.
@RestController
public class UserResource implements UserService {
}
UserClient.java.
package project.user;
@FeignClient("users")
public interface UserClient extends UserService {
}
通常不建议在服务器和 client 之间共享接口。它引入了紧耦合,实际上也不能与当前形式的 Spring MVC 一起使用(方法参数映射不会被继承)。
17.8 Feign request/response 压缩
您可以考虑为 Feign 请求启用请求或响应 GZIP 压缩。您可以通过启用其中一个 properties 来执行此操作:
feign.compression.request.enabled=true
feign.compression.response.enabled=true
Feign 请求压缩为您提供类似于您为 web 服务器设置的设置:
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
这些 properties 允许您选择压缩媒体类型和最小请求阈值长度。
17.9 Feign logging
为每个创建的 Feign client 创建一个 logger。默认情况下,logger 的 name 是用于创建 Feign client 的接口的完整 class name。 Feign logging 仅响应DEBUG
level。
application.yml.
logging.level.project.user.UserClient: DEBUG
您可以根据 client 配置Logger.Level
object,告诉 Feign 要 log 多少。选择是:
NONE
,没有 logging(DEFAULT)。BASIC
,Log 只有请求方法和 URL 以及响应状态 code 和执行 time。HEADERS
,记录基本信息以及请求和响应 headers。FULL
,记录请求和响应的 headers,body 和元数据。
对于 example,以下将Logger.Level
设置为FULL
:
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}