关于Spring Cloud Feign
,一个核心概念是命名客户端(named client
)。每个feign client
可以被理解成是一整套组件的一部分,这套组件一块工作,按需跟远程服务器发生联系,这整套组件有一个名字,就是应用开发人员通过@FeignClient
所指定的名字。Spring Cloud
根据FeignClientsConfiguration
配置,针对每一个命名的feign
客户端,将它的这整套组件创建为一个ApplicationContext
实例存在。这里面包含了一个feign.Decoder
,一个feign.Encoder
和一个feign.Contract
。并且使用注解@FeignClient
的属性contextId
可以覆盖这整套组件的名称。
通过使用@FeignClient
注解属性configuration
增加额外的配置,Spring Cloud
允许你完全控制feign
客户端。例子如下 :
// 这里 FooConfiguration 会叠加在缺省 FeignClientsConfiguration 之上
// 对 feign client stores 生效
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
该例子中的feign
客户端会结合考虑FeignClientsConfiguration
配置和自定义配置FooConfiguration
最终生成,并且FooConfiguration
配置中的定义优先级高。
注意 :
FooConfiguration
不必要使用注解@Configuration
。不过如果FooConfiguration
使用了注解@Configuration,
则注意把它从@ComponentScan
中排除掉,要不然该配置会被@ComponentScan
包含进来,这样它就会变成feign.Decoder
,feign.Encoder
,feign.Contract
等组件的缺省来源。想避免FooConfiguration
被@ComponentScan
看到,可以将它放到一个独立的,跟任何@ComponentScan
/@SpringBootApplication
可见包都不重叠的包中。或者将它明确地从@ComponentScan
注解中排除。
注意 :
serviceId
属性现在已经不建议使用了,推荐使用name
。
注意 :
@FeignClient
注解属性contextId
可以用于修改整个套组ApplicationContext
的名字,它会覆盖客户端名称的别名,也会被用作该feign
客户端配置bean
名称的一部分。
注意 : 以前使用
url
属性,不需要name
属性。现在必须要使用name
属性。
@FeignClient
的name
/url
属性值可以使用占位符,如下所示:
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
Spring Cloud Netflix
为feign
客户端缺省提供以下bean
组件:
BeanType |
beanName |
ClassName |
---|---|---|
Decoder |
feignDecoder |
ResponseEntityDecoder (包了一个SpringDecoder ) |
Encoder |
feignEncoder |
SpringEncoder |
Logger |
feignLogger |
Slf4jLogger |
Contract |
feignContract |
SpringMvcContract |
Feign.Builder |
feignBuilder |
HystrixFeign.Builder |
Client |
feignClient |
如果启用了Ribbon 会是LoadBalancerFeignClient ,否则会使用缺省的client |
把OkHttpClient
或者ApacheHttpClient
放在classpath
中,并且相应设置feign.okhttp.enabled
或者feign.httpclient.enabled
为true
, 那就会使用相应的这些HTTP
客户端。想定制HTTP
客户端的话,使用ApacheHttpClient
时,你可以提供一个类型为ClosableHttpClient
的bean
;使用OK HTTP
的话,提供一个类型为OkHttpClient
的bean
。
缺省情况下,Spring Cloud Netflix
没有给feign
提供以下bean
组件,但是它仍然会从应用上下文中查找这些bean
组件用于创建feign
客户端 (这里其实给开发人员提供了定制feign
客户端的机会):
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection
SetterFactory
在@FeignClient
配置(比如上面提到的配置FooConfiguration
)中定义这里描述的任何一种类型的bean
,你就可以覆盖框架缺省提供的这种类型的bean
。比如 :
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
通过该例子,开发人员指定使用feign.Contract.Default
而不再使用Spring Cloud
缺省的SpringMvcContract
。并且开发人员往RequestInterceptor
集合中增加了一个自定义的RequestInterceptor
:BasicAuthRequestInterceptor
。
@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
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
通过注解@EnableFeignClients
的属性defaultConfiguration
可以为所有feign
客户端提供缺省配置,配置方式跟上面提到的方式类似。区别是缺省属性是面向所有feign
客户端的。
如果你喜欢使用配置文件配置所有@FeignClient
,你可以使用缺省配置属性,也就是feign
客户端名称为default
,例子如下所示 :
application.yml
配置文件内容 :
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
如果你既定义了@Configuration
配置类bean
又使用了配置属性,配置属性会被优先使用,它会覆盖@Configuration
配置类bean
中的值。但是如果你想指定使用@Configuration
,可以设置feign.client.default-to-properties
为false
。
如果你想在RequestInterceptor
中使用ThreadLocal
绑定变量,你需要将Hystrix
的线程隔离策略设置为SEMAPHORE
或者在Feign
中禁用Hystrix
:
application.yml`配置文件内容 :
# 禁用Feign中的Hystrix:
feign:
hystrix:
enabled: false
# 设置线程隔离级别为SEMAPHORE:
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
如果你想创建多个同名或者url
相同的feign
客户端以确保它们指向同一个服务器但是又要它们使用不同的配置,那么我们必须使用不同的contextId
以避免这些配置bean
的名字冲突。例子如下所示 :
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
//..
}
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
//..
}