声明式 REST 客户端:Feign
Feign 是一个声明式 Web 服务客户端。它使编写 Web 服务客户端变得更加容易。要使用 Feign 创建一个接口并对其进行注释。它具有可插入的注释支持,包括 Feign 注释和 JAX-RS 注释。 Feign 还支持可插入的编码器和解码器。 Spring Cloud 添加了对 Spring MVC 注释以及使用 Spring Web 中默认使用的相同 HttpMessageConverters
的支持。 Spring Cloud集成了Eureka、Spring Cloud CircuitBreaker以及Spring Cloud LoadBalancer,在使用Feign时提供负载均衡的http客户端。
如何包含 Feign
要将 Feign 包含在您的项目中,请使用带有组 org.springframework.cloud
和工件 ID spring-cloud-starter-openfeign
的启动器。有关使用当前 Spring Cloud 发布系列设置构建系统的详细信息,请参阅 Spring Cloud 项目页面。
示例 Spring Boot 应用程序
@SpringBootApplication
@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();
@GetMapping("/stores")
Page<Store> getStores(Pageable pageable);
@PostMapping(value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
@DeleteMapping("/stores/{storeId:\\d+}")
void delete(@PathVariable Long storeId);
}
在 @FeignClient
注释中,字符串值(上面的“stores”)是任意客户端名称,用于创建 Spring Cloud LoadBalancer 客户端。您还可以使用 url
属性(绝对值或只是主机名)指定 URL。应用程序上下文中 bean 的名称是接口的完全限定名称。要指定您自己的别名值,您可以使用 @FeignClient
注释的 qualifiers
值。
上面的负载平衡器客户端将希望发现“stores”服务的物理地址。如果您的应用程序是 Eureka 客户端,那么它将解析 Eureka 服务注册表中的服务。如果您不想使用 Eureka,您可以使用 SimpleDiscoveryClient
在外部配置中配置服务器列表。
Spring Cloud OpenFeign 支持 Spring Cloud LoadBalancer 阻塞模式的所有可用功能。
属性解析方式
在创建 Feign
客户端 bean 时,我们解析通过 @FeignClient
注释传递的值。从 4.x
开始,这些值正在被急切地解析。对于大多数用例来说,这是一个很好的解决方案,并且它还允许 AOT 支持。
如果您需要延迟解析属性,请将 spring.cloud.openfeign.lazy-attributes-resolution
属性值设置为 true
。
覆盖 Feign 默认值
Spring Cloud Feign 支持的一个核心概念是指定客户端。每个假客户端都是组件集合的一部分,这些组件协同工作以按需联系远程服务器,并且该集合有一个名称,您作为应用程序开发人员使用 @FeignClient
注释为其指定名称。 Spring Cloud 使用 FeignClientsConfiguration
按需为每个指定客户端创建一个新的集合作为 ApplicationContext
。其中包含(除其他外)一个 feign.Decoder
、一个 feign.Encoder
和一个 feign.Contract
。可以使用 @FeignClient
注释的 contextId
属性覆盖该集合的名称。
Spring Cloud 允许您通过使用 @FeignClient
声明附加配置(在 FeignClientsConfiguration
之上)来完全控制 feign 客户端。例子:
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
在这种情况下,客户端由 FeignClientsConfiguration
中已有的组件以及 FooConfiguration
中的任何组件组成(后者将覆盖前者)。
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi
{
/**
* 新增一条支付相关流水记录
* @param payDTO
* @return
*/
@PostMapping("/pay/add")
public ResultData addPay(@RequestBody PayDTO payDTO);
/**
* 按照主键记录查询支付流水信息
* @param id
* @return
*/
@GetMapping("/pay/get/{id}")
public ResultData getPayInfo(@PathVariable("id") Integer id);
/**
* openfeign天然支持负载均衡演示
* @return
*/
@GetMapping(value = "/pay/get/info")
public String mylb();
}
name
和 url
属性支持占位符。以前,使用 url
属性不需要 name
属性。现在需要使用 name
。
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
Spring Cloud OpenFeign默认不为feign提供以下bean,但仍然从应用程序上下文中查找这些类型的bean来创建feign客户端:
-
Logger.Level
-
Retryer
-
ErrorDecoder
-
Request.Options
-
Collection<RequestInterceptor>
-
SetterFactory
-
QueryMapEncoder
-
Capability
(MicrometerObservationCapability
andCachingCapability
are provided by default)Capability
(默认提供MicrometerObservationCapability
和CachingCapability
)
默认情况下会创建类型为 Retryer
的 Retryer.NEVER_RETRY
bean,这将禁用重试。请注意,此重试行为与 Feign 默认行为不同,Feign 会自动重试 IOException,将它们视为与网络相关的瞬态异常,以及从 ErrorDecoder 抛出的任何 RetryableException。
开启
package com.atguigu.cloud.config;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @auther zzyy
* @create 2023-11-10 11:09
*/
@Configuration
public class FeignConfig
{
@Bean
public Retryer myRetryer()
{
//return Retryer.NEVER_RETRY; //Feign默认配置是不走重试策略的
//最大请求次数为3(1+2),初始间隔时间为100ms,重试间最大间隔时间为1s
return new Retryer.Default(100,1,3);
}
}
创建这些类型之一的 bean 并将其放置在 @FeignClient
配置中(例如上面的 FooConfiguration
)允许您覆盖所描述的每一个 bean。例子:
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
超时处理
我们可以在默认客户端和指定客户端上配置超时。 OpenFeign 使用两个超时参数:
connectTimeout
防止由于服务器处理时间过长而阻塞调用者。
readTimeout
从连接建立时开始应用,当返回响应时间过长时触发。
单独某个服务
全局配置
手动创建客户端
在某些情况下,可能需要以使用上述方法无法实现的方式自定义您的 Feign 客户端。在这种情况下,您可以使用 Feign Builder API 创建客户端。下面是一个示例,它创建两个具有相同接口的 Feign 客户端,但为每个客户端配置一个单独的请求拦截器。
@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");
}
}
PROD-SVC
是客户端将向其发出请求的服务的名称。
Feign Spring Cloud 断路器支持
如果 Spring Cloud CircuitBreaker 位于类路径中并且 spring.cloud.openfeign.circuitbreaker.enabled=true
,Feign 会用断路器包装所有方法。
要在每个客户端上禁用 Spring Cloud CircuitBreaker 支持,请创建一个具有“原型”范围的普通 Feign.Builder
,例如:
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
断路器名称遵循此模式 <feignClientClassName>#<calledMethod>(<parameterTypes>)
。当使用 FooClient
接口调用 @FeignClient
且被调用的无参数接口方法为 bar
时,断路器名称将为 FooClient#bar()
。
提供 CircuitBreakerNameResolver
的 bean,您可以更改断路器名称模式。
@Configuration
public class FooConfiguration {
@Bean
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
}
}
使用配置属性配置断路器
您可以通过配置属性配置断路器。
例如,如果您有这个 Feign 客户端
@FeignClient(url = "http://localhost:8080")
public interface DemoClient {
@GetMapping("demo")
String getDemo();
}
您可以通过执行以下操作使用配置属性来配置它
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true
alphanumeric-ids:
enabled: true
resilience4j:
circuitbreaker:
instances:
DemoClientgetDemo:
minimumNumberOfCalls: 69
timelimiter:
instances:
DemoClientgetDemo:
timeoutDuration: 10s
Feign Spring Cloud 断路器回退
Spring Cloud CircuitBreaker 支持回退的概念:当回路打开或出现错误时执行的默认代码路径。要为给定的 @FeignClient
启用回退,请将 fallback
属性设置为实现回退的类名称。您还需要将您的实现声明为 Spring bean。
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {
@GetMapping("/hello")
Hello getHello();
@GetMapping("/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";
}
}
Feign and @Primary
当使用 Feign 和 Spring Cloud CircuitBreaker 回退时, ApplicationContext
中有多个相同类型的 bean。这将导致 @Autowired
无法工作,因为没有一个 Bean 或标记为主要的 Bean。为了解决这个问题,Spring Cloud OpenFeign 将所有 Feign 实例标记为 @Primary
,因此 Spring Framework 将知道要注入哪个 bean。在某些情况下,这可能是不可取的。要关闭此行为,请将 @FeignClient
的 primary
属性设置为 false。
@FeignClient(name = "hello", primary = false)
public interface HelloClient {
// methods here
}
Feign 继承支持
Feign 通过单继承接口支持样板 API。这允许将常见操作分组到方便的基本接口中。
UserService.java
public interface UserService {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") long id);
}
UserResource.java
@RestController
public class UserResource implements UserService {
}
UserClient.java
@FeignClient("users")
public interface UserClient extends UserService {
}
Feign 请求/响应压缩
@FeignClient
接口不应在服务器和客户端之间共享,并且不再支持在类级别使用 @RequestMapping
注释 @FeignClient
接口。
可以考虑为您的 Feign 请求启用请求或响应 GZIP 压缩。您可以通过启用以下属性之一来做到这一点:
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.response.enabled=true
Feign 请求压缩为您提供的设置类似于您为 Web 服务器设置的设置:
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
这些属性允许您选择压缩媒体类型和最小请求阈值长度。
Feign logging 日志记录
为每个创建的 Feign 客户端创建一个记录器。默认情况下,记录器的名称是用于创建 Feign 客户端的接口的完整类名。 Feign 日志记录仅响应 DEBUG
级别。
logging.level.project.user.UserClient: DEBUG
您可以为每个客户端配置的 Logger.Level
对象告诉 Feign 要记录多少内容。选项有:
-
NONE
,不记录(默认)。 -
BASIC
,仅记录请求方法和URL以及响应状态码和执行时间。 -
HEADERS
,记录基本信息以及请求和响应标头。 -
FULL
,记录请求和响应的标头、正文和元数据。
例如,以下将 Logger.Level
设置为 FULL
:
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
Feign 能力支持
Feign 能力暴露核心 Feign 组件,以便可以修改这些组件。例如,这些功能可以获取 Client
,对其进行修饰,然后将修饰后的实例返回给 Feign。对 Micrometer 的支持就是一个很好的现实例子。
创建一个或多个 Capability
bean 并将它们放入 @FeignClient
配置中可以让您注册它们并修改相关客户端的行为。
@Configuration
public class FooConfiguration {
@Bean
Capability customCapability() {
return new CustomCapability();
}
}
Micrometer Support
如果满足以下所有条件,则会创建并注册一个 MicrometerObservationCapability
bean,以便 Micrometer 可以观察您的 Feign 客户端:
feign-micrometer
位于类路径上
ObservationRegistry
bean 可用
feign 千分尺属性设置为 true
(默认情况下)
spring.cloud.openfeign.micrometer.enabled=true
(适用于所有客户端)
spring.cloud.openfeign.client.config.feignName.micrometer.enabled=true
(对于单个客户端)
Feign 缓存
如果使用 @EnableCaching
注释,则会创建并注册一个 CachingCapability
bean,以便您的 Feign 客户端识别其接口上的 @Cache*
注释:
public interface DemoClient {
@GetMapping("/demo/{filterParam}")
@Cacheable(cacheNames = "demo-cache", key = "#keyParam")
String demoEndpoint(String keyParam, @PathVariable String filterParam);
}
您还可以通过属性 spring.cloud.openfeign.cache.enabled=false
禁用该功能。
Feign @QueryMap 支持
Spring Cloud OpenFeign 提供了等效的 @SpringQueryMap
注解,用于将 POJO 或 Map 参数注解为查询参数映射。
例如, Params
类定义参数 param1
和 param2
:
// Params.java
public class Params {
private String param1;
private String param2;
// [Getters and setters omitted for brevity]
}
以下 feign 客户端通过使用 @SpringQueryMap
注释来使用 Params
类:
@FeignClient("demo")
public interface DemoTemplate {
@GetMapping(path = "/demo")
String demoEndpoint(@SpringQueryMap Params params);
}
OpenFeign默认HttpClient修改
使用Apache HttpClient5替换,
引入依赖
<!-- feign-hc5-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>
<version>13.1</version>
</dependency>
修改yml配置
# Apache HttpClient5 配置开启
spring:
cloud:
openfeign:
httpclient:
hc5:
enabled: true