Spring Cloud OpenFeign Features
Declarative REST Client:Feign
Feign是一个声明式的web服务客户端,创建一个interface并添加注解就可以创建一个web服务客户端。提供了可插拔的注解包含Feign的注解和JAX-RS的注解,Feign同样支持可插拔的编码解码器。SpringCloud为Spring MVC的注解提供了支持并且默认使用同样的HttpMessageConverters。当使用Feign时,SpringCloud集成了Eureka,Spring Cloud CircuitBreaker以及Spring Cloud LoadBalancer来为Feign提供负载均衡方面的支持。
How to Include Feign
添加group为org.springframework.cloud
,artifact id为spring-cloud-starter-openfeign
的starter依赖。Spring Cloud Project page。查看Spring Cloud Project page以查看构建细节。
SpringBoot app 示例:
java
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
StoreClient.java
java
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();
@RequestMapping(method = RequestMethod.GET, value = "/stores")
Page<Store> getStores(Pageable pageable);
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
@RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
void delete(@PathVariable Long storeId);
}
在 @FeignClient
注解中,"stores"是用来创建一个 Spring Cloud LoadBalancer client。你也可以使用url
参数制定一个URL。这个bean在application context的名称是这个interface的全限定类名。为了指定你自己的别名,你可以使用@FeignClient
的qualifiers值。
load-balancer client会根据"stores"来找到物理的地址。如果你的应用是一个Eureka client,那么就会用过Eureka注册中心来解析"stores"。如果你不想使用Eureka,你可以通过SimpleDiscoveryClient
在你的外部配置中定义一系列servers。
SpringCloud OpenFeign支持Spring Cloud LoadBanlancer的阻塞模式的所有Feature。更多信息请参考project documentation。
Tip
要在@Configuration
上使用注解 @EnableFeignClients
,请确保指定client的位置,例如 @EnableFeignClients(basePackages = "com.example.clients")
或者把他们详细列出来: @EnableFeignClients(clients = InventoryServiceFeignClient.class)
Attribute resolution mode
当创建Feign
client bean的时候,我们处理通过@FeignClient
注解传入的value。对于4.x,values会被立即处理,这对于大多数的使用场景都是好的解决方式,而且还支持AOT。
如果你希望这些参数被懒加载式处理,设置spring.cloud.openfeign.lazy-attributes-resolution=true
。
Overriding Feign Default
SpringCloud feign的一个核心概念就是具名客户端,每个feign client都作为一组组件的一部分来发送请求,并且这一组组件的名字由@FeignClient
注解所给。Spring Cloud会使用FeignClientsConfiguration
来创建出一组组件作为ApplicationContext
,其中包含一个feign.Decoder
,一个feign.Encoder
和一个feign.Contract
。可以使用@FeignClient
注解的contextId
参数来覆盖这一组组件的名字。
SpringCloud可以让你完全控制feign客户端,通过声明额外的配置(其在FeignClientsConfiguration
之上),如下所示
java
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
在这个事例中 FooConfiguration
的配置会覆盖FeignClientsConfiguration
的配置。
Note
FooConfiguration
不需要被@Configuration
注解。然而,如果被@Configuration
注解了,要注意不要被@ComponentScan
扫描到,否则它将会作为feign.Decoder
, feign.Encoder
, feign.Contract
等,默认的配置。你可以将他们放在与@ComponentScan
或@SpringBootApplication
分离的包下,或者显式地从@ComponentScan
排除掉。
Note
使用@FeignClient
注解的contextId
参数除了改变ApplicationContext
的名字外,还会覆盖client的别名,还会作为client所创建的configuration bean名称的一部分。
Warning
之前,使用url
参数时,不必要有name参数,但是现在是必要的。
name
和url
参数是支持占位符的
java
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
SpringCloud OpenFeign默认提供了系列的bean (BeanType
beanName: ClassName
)
Decoder
feignDecoder:ResponseEntityDecoder
(which wraps aSpringDecoder
)Encoder
feignEncoder:SpringEncoder
Logger
feignLogger:Slf4jLogger
MicrometerObservationCapability
micrometerObservationCapability: Iffeign-micrometer
is on the classpath andObservationRegistry
is availableMicrometerCapability
micrometerCapability: Iffeign-micrometer
is on the classpath,MeterRegistry
is available andObservationRegistry
is not availableCachingCapability
cachingCapability: If@EnableCaching
annotation is used. Can be disabled viaspring.cloud.openfeign.cache.enabled
.Contract
feignContract:SpringMvcContract
Feign.Builder
feignBuilder:FeignCircuitBreaker.Builder
Client
feignClient: 如果SpringCloud LoadBanlancer在classpath上,会使用FeignBlockingLoadBalancerClient
,如果不在classpath上,就会使用默认的feign client。
Note
spring-cloud-starter-openfeign
支持 spring-cloud-starter-loadbalancer
,然而,作为一个可选的依赖,如果想使用它请确保该依赖在你的classpath下。
OkHttpClient, Apache HttpClient 5 和Http2Client, Feign clients可以通过设置相应配置并添加对应依赖来启用相应HttpClient, spring.cloud.openfeign.okhttp.enabled
或spring.cloud.openfeign.httpclient.hc5.enabled
或spring.cloud.openfeign.http2client.enabled
。
你可以进一步通过配置项来进一步对http clients进行自定义。例如spring.cloud.openfeign.httpclient.xxx
以httpclient
为前缀的配置项将会应用于所有客户端,以httpclient.hc5
为前缀的将应用于Apache HttpClient5
。以httpclient.okhttp
为前缀的将应用于OkHttpClient
。以httpclient.http2
为前缀的将应用于Http2Client
。可以在附录中查询完整的配置列表。对于HttpClient 5 ,你还可以实现HttpClientBuilderCustomizer
接口来实现编码式配置。
Tip
从Spring Cloud OpenFeign4 开始,Feign Apache HttpClient 4 不再被支持。所以我们建议以使用Apache HttpClient 5。
Spring Cloud OpenFeign默认并没有提供下面的bean,但是在创建feign client的时候会在application context中查找这些类型的bean。
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection<RequestInterceptor>
SetterFactory
QueryMapEncoder
Capability
(MicrometerObservationCapability
andCachingCapability
are provided by default)
Retryer
类型的 Retryer.NEVER_RETRY
bean会被默认创建,它会禁用掉重试。需要注意的是,这种重试行为与Feign默认行为是不一样的。默认的将会自动重试IOExceptions,将它们看做短暂的网络相关的异常,还会重试那些从ErrorDecoder中抛出的任何RetryableException 。
创建这些类型的bean,并将它们放置在@FeignClient
配置中(像上面的 FooConfiguration
一样)。这样你就可以覆盖每一个上述的bean。例如
java
@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
代替SpringMvcContract
,并添加一个RequestInterceptor
到RequestInterceptor
的集合中。
@FeignClient
还可以使用configuration properties进行配置
yml
spring:
cloud:
openfeign:
client:
config:
feignName:
url: http://remote-service.com
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
defaultQueryParameters:
query: queryValue
defaultRequestHeaders:
header: headerValue
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
responseInterceptor: com.example.BazResponseInterceptor
dismiss404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
capabilities:
- com.example.FooCapability
- com.example.BarCapability
queryMapEncoder: com.example.SimpleQueryMapEncoder
micrometer.enabled: false
例子中的feignName
指的是 @FeignClient
的value
,也可以是@FeignClient
的name
,@FeignClient
的contextId
。在负载均衡场景下,它相当于server app的serviceId
,用于拿取对应的实例。在其中所配置的哪些类必须要在Spring Context中有一个bean或者有一个默认的构造函数。
可以在@EnableFeignClients
注解的defaultConfiguration
参数中指定默认的配置,它会对所有的feign client生效。
如果你倾向于使用配置项来配置所有的@FeignClient
,那么你可以以default
作为feign 名称进行配置。
你可以使用spring.cloud.openfeign.client.config.feignName.defaultQueryParameters
和spring.cloud.openfeign.client.config.feignName.defaultRequestHeaders
来指定名为feignName
的client每次请求所携带的query paramters和headers。
application.yml
yml
spring:
cloud:
openfeign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
如果同时创建了 @Configuration
bean和配置文件,那么会使用配置文件进行配置。但是你如果想改成 @Configuration
优先的形式,你可以设置 spring.cloud.openfeign.client.default-to-properties=false
。
如果你想创建具有相同name或url的feign client,让他们指向同一server,但是又具有不同的自定义配置,你可以使用@FeignClient
的contextId
参数来避免这些configuration bean的冲突。
java
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
//..
}
java
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
//..
}
你可以配置FeignClient 不从父Context中继承beans。你可以覆盖FeignClientConfigurer
bean中的inheritParentConfiguration()
为false。
java
@Configuration
public class CustomConfiguration {
@Bean
public FeignClientConfigurer feignClientConfigurer() {
return new FeignClientConfigurer() {
@Override
public boolean inheritParentConfiguration() {
return false;
}
};
}
}
Tip
默认情况下,Feign client不会对/
进行编码,你可以通过设置spring.cloud.openfeign.client.decodeSlash=false
来改变该行为。
SpringEncoder
configuration
在我们提供的SpringEncoder
中,我们为二进制内容设置了空字符集,对于其他设置了UTF-8
字符集。
Timeout Handling
我们可以为默认和named client上配置超时时间。OpenFeign可以使用两种超时参数
connectTimeout
防止由于服务器处理时间过长而阻塞调用者readTimeout
是从连接建立之后,返回response时间过长是被触发
Creating Feign Clients Manually
你可以通过Feign Builder API来创建Client。下面示例展示了使用相同接口却配置了不同请求拦截器的Feign Client。
java
@Import(FeignClientsConfiguration.class)
class FooController {
private FooClient fooClient;
private FooClient adminClient;
@Autowired
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "https://PROD-SVC");
this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "https://PROD-SVC");
}
}
Note
在上面示例中,FeignClientsConfiguration.class
是由 Spring Cloud OpenFeign
提供的默认配置。
Note
PROD-SVC
是client所请求服务的name
Note
Feign的 Contract
对象定义了什么注解是在接口中可用的。autowired Contract
bean为SpringMVC注解提供了支持而不是使用Feign原生的注解。
你同样可以使用Builder
来配置FeignClient不从父Context中继承beans,通过调用 inheritParentContext(false)
。
Feign Spring Cloud CircuitBreaker Support(对熔断器的支持)
如果Spring Cloud CircuitBreaker 在classpath中,并且设置了spring.cloud.openfeign.circuitbreaker.enabled=true
,那么Feign会为所有的方法用熔断器包裹。
想要在每个client上禁用 Spring Cloud CircuitBreaker,可以创建一个普通的Feign.Builder
,设置其为prototype
java
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
熔断器的命名遵循的规则是<feignClientClassName>#<calledMethod>(<parameterTypes>)
。当调用FooClient
中的为bar
的方法且无参数时,那这个熔断器会命名为FooClient#bar()
。
Note
从2020.0.2开始,熔断器的名称就从<feignClientName>_<calledMethod>
改变了。根据2020.0.4的介绍,可以使用CircuitBreakerNameResolver
自定义命名模式。
java
@Configuration
public class FooConfiguration {
@Bean
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
}
}
为了启用SpringCloud CircuitBreaker group,设置spring.cloud.openfeign.circuitbreaker.group.enabled=true
。
Configuring CircuitBreakers With Configuration Properties
你可以通过configuration properties来对CircuitBreakers 进行配置
例如,你有个Feign client定义如下
java
@FeignClient(url = "http://localhost:8080")
public interface DemoClient {
@GetMapping("demo")
String getDemo();
}
你可以对其使用configuration properties配置
yaml
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true
alphanumeric-ids:
enabled: true
resilience4j:
circuitbreaker:
instances:
DemoClientgetDemo:
minimumNumberOfCalls: 69
timelimiter:
instances:
DemoClientgetDemo:
timeoutDuration: 10s
Note
如果你想要回到Spring Cloud 2022.0.0之前熔断器命名的方式,你可以设置spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled=true
Feign Spring Cloud CircuitBreaker Fallbacks
Spring Cloud CircuitBreaker 支持fallback的模式,即当连接不通或者发生错误时,会执行特定的代码。为了对@FeignClient
启用fallback,可以添加实现fallback的类,并将类名设置到fallback
参数中。需要注意实现fallback的类应注册到Spring中作为bean。
java
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class Fallback implements TestClient {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
如果想要知道是什么具体原因触发了fallback,可以使用 @FeignClient
的 fallbackFactory
参数。
java
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {
@Override
public FallbackWithFactory create(Throwable cause) {
return new FallbackWithFactory();
}
}
static class FallbackWithFactory implements TestClientWithFactory {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
Feign and @Primary
当在Feign中使用Spring Cloud CircuitBreaker fallback时,在ApplicationContext
中对于同一类型会有多个bean。这会导致@Autowired
工作不正常。因此SpringCloud OpenFeign会把所有Feign示例标记为@Primary
,这样Spring框架就知道应该注入哪个bean。如果你不需要该特性,可以将@FeignClient
的primary
参数设置为false。
java
@FeignClient(name = "hello", primary = false)
public interface HelloClient {
// methods here
}
Feign Inheritance Support
Feign支持通过继承interface来实现模板api的功能,这样就可以方便地将常用的操作分别放在一些base interface中。
java
public interface UserService {
@RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
User getUser(@PathVariable("id") long id);
}
java
@RestController
public class UserResource implements UserService {
}
java
@FeignClient("users")
public interface UserClient extends UserService {
}
Warning
@FeignClient
interfaces不应该在server和client之间共享,并且在@FeignClient
在类层面用@RequestMapping
注解是不被支持的。
Feign request/response compression
你也许想对你的Feign请求响应开启GZIP压缩功能,可以进行如下配置
properties
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.response.enabled=true
Feign请求的压缩设置与对web server的设置很相似
properties
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json
spring.cloud.openfeign.compression.request.min-request-size=2048
这些配置让你可以修改压缩的media type和最小请求大小阈值。
Feign logging
对于每一个FeignClient,都会有一个相应的logger,logger的名称是创建feign client的interface的全类名。Feign logging只对应DEBUG
level。
properties
logging.level.project.user.UserClient: DEBUG
Logger.Level
对象用来告诉Feign怎么去log
NONE
, 不打印日志(默认)BASIC
, 请求方法和URL,以及响应status code和执行时间HEADERS
, 打印basis日志,还有请求和响应headers。FULL
, 打印请求和响应的headers,body以及元数据
例如,如下设置Logger.Level
为FULL
:
java
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}