Spring Cloud OpenFeign 功能

声明式 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,请使用 group 为org.springframework.cloud,artifact 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",
				params = "mode=upsert")
	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 的阻塞模式下可用的所有功能。您可以在项目文档中阅读更多相关信息。

要在@Configuration注释的类上使用@EnableFeignClients注释,请确保指定客户端的位置,例如:@EnableFeignClients(basePackages = "com.example.clients")或显式列出它们:@EnableFeignClients(clients = InventoryServiceFeignClient.class)
由于FactoryBean对象可能在初始上下文刷新发生之前被实例化,并且 Spring Cloud OpenFeign 客户端的实例化会触发上下文刷新,因此不应在FactoryBean类中声明它们。

属性解析模式

在创建Feign客户端 bean 时,我们解析通过@FeignClient注释传递的值。从4.x开始,这些值将被积极地解析。这对于大多数用例来说是一个不错的解决方案,它还允许 AOT 支持。

如果您需要延迟解析属性,请将spring.cloud.openfeign.lazy-attributes-resolution属性值设置为true

对于 Spring Cloud Contract 测试集成,应使用延迟属性解析。

覆盖 Feign 默认值

Spring Cloud 的 Feign 支持中的一个核心概念是命名客户端。每个 feign 客户端都是一组协同工作的组件的一部分,这些组件按需联系远程服务器,并且该组件组有一个名称,您作为应用程序开发人员可以使用@FeignClient注释来赋予它。Spring Cloud 使用FeignClientsConfiguration按需为每个命名客户端创建一个新的组件组作为ApplicationContext。这包含(除其他事项外)feign.Decoderfeign.Encoderfeign.Contract。可以使用@FeignClient注释的contextId属性来覆盖该组件组的名称。

Spring Cloud 允许您通过使用@FeignClient声明其他配置(在FeignClientsConfiguration之上)来完全控制 feign 客户端。示例

@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
	//..
}

在这种情况下,客户端由FeignClientsConfiguration中已有的组件以及FooConfiguration中的任何组件组成(后者将覆盖前者)。

FooConfiguration不需要用@Configuration进行注释。但是,如果它被注释了,那么要注意将其从任何@ComponentScan中排除,否则@ComponentScan将包含此配置,因为它将成为feign.Decoderfeign.Encoderfeign.Contract等的默认来源。可以通过将其放在与任何@ComponentScan@SpringBootApplication不重叠的单独包中来避免这种情况,或者可以在@ComponentScan中显式排除它。
除了更改ApplicationContext组件组的名称外,还使用@FeignClient注释的contextId属性,它将覆盖客户端名称的别名,并将用作为此客户端创建的配置 bean 名称的一部分。
以前,使用url属性不需要name属性。现在需要使用name

nameurl属性支持占位符。

@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
	//..
}

Spring Cloud OpenFeign 默认情况下为 feign 提供以下 bean(BeanType beanName:ClassName

  • Decoder feignDecoder:ResponseEntityDecoder(它包装了一个SpringDecoder

  • Encoder feignEncoder:SpringEncoder

  • Logger feignLogger:Slf4jLogger

  • MicrometerObservationCapability micrometerObservationCapability:如果feign-micrometer位于类路径上且ObservationRegistry可用

  • MicrometerCapability micrometerCapability:如果feign-micrometer位于类路径上,MeterRegistry可用且ObservationRegistry不可用

  • CachingCapability 缓存能力:如果使用了@EnableCaching 注解。可以通过spring.cloud.openfeign.cache.enabled禁用。

  • Contract feign契约:SpringMvcContract

  • Feign.Builder feign构建器:FeignCircuitBreaker.Builder

  • Client feign客户端:如果Spring Cloud LoadBalancer在类路径上,则使用FeignBlockingLoadBalancerClient。如果两者都不在类路径上,则使用默认的feign客户端。

spring-cloud-starter-openfeign 支持 spring-cloud-starter-loadbalancer。但是,这是一个可选依赖项,如果您想使用它,则需要确保已将其添加到您的项目中。

要使用OkHttpClient支持的Feign客户端和Http2Client Feign客户端,请确保您要使用的客户端位于类路径上,并将spring.cloud.openfeign.okhttp.enabledspring.cloud.openfeign.http2client.enabled分别设置为true

对于Apache HttpClient 5支持的Feign客户端,确保HttpClient 5位于类路径上就足够了,但是您仍然可以通过将spring.cloud.openfeign.httpclient.hc5.enabled设置为false来禁用其在Feign客户端中的使用。您可以通过提供org.apache.hc.client5.http.impl.classic.CloseableHttpClient的bean来自定义使用的HTTP客户端(当使用Apache HC5时)。

您可以通过设置spring.cloud.openfeign.httpclient.xxx属性中的值来进一步自定义http客户端。仅以httpclient为前缀的属性将适用于所有客户端;以httpclient.hc5为前缀的属性适用于Apache HttpClient 5;以httpclient.okhttp为前缀的属性适用于OkHttpClient;以httpclient.http2为前缀的属性适用于Http2Client。您可以在附录中找到可以自定义的属性的完整列表。如果您无法使用属性配置Apache HttpClient 5,则可以使用HttpClientBuilderCustomizer接口进行编程配置。

从Spring Cloud OpenFeign 4开始,不再支持Feign Apache HttpClient 4。我们建议改用Apache HttpClient 5。

Spring Cloud OpenFeign默认情况下不提供以下feign bean,但仍然会从应用程序上下文查找这些类型的bean来创建feign客户端

  • Logger.Level

  • Retryer

  • ErrorDecoder

  • Request.Options

  • Collection<RequestInterceptor>

  • SetterFactory

  • QueryMapEncoder

  • Capability(默认情况下提供MicrometerObservationCapabilityCachingCapability

默认情况下会创建一个类型为RetryerRetryer.NEVER_RETRY bean,这将禁用重试。请注意,此重试行为与Feign的默认行为不同,Feign的默认行为会自动重试IOException,将其视为瞬态网络相关异常,以及ErrorDecoder抛出的任何RetryableException。

创建一个这些类型之一的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");
	}
}

这将SpringMvcContract替换为feign.Contract.Default,并将RequestInterceptor添加到RequestInterceptor集合中。

@FeignClient也可以使用配置属性进行配置。

application.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指的是@FeignClientvalue,它也与@FeignClientname@FeignClientcontextId别名。在负载均衡场景中,它还对应于将用于检索实例的服务器应用程序的serviceId。解码器、重试器和其他类的指定类必须在Spring上下文中具有bean或具有默认构造函数。

可以在@EnableFeignClients属性defaultConfiguration中指定默认配置,方式与上面描述的类似。不同之处在于,此配置将应用于所有feign客户端。

如果您更喜欢使用配置属性来配置所有@FeignClient,您可以创建具有default feign名称的配置属性。

您可以使用spring.cloud.openfeign.client.config.feignName.defaultQueryParametersspring.cloud.openfeign.client.config.feignName.defaultRequestHeaders来指定将与名为feignName的客户端的每个请求一起发送的查询参数和标头。

application.yml

spring:
	cloud:
		openfeign:
			client:
				config:
					default:
						connectTimeout: 5000
						readTimeout: 5000
						loggerLevel: basic

如果我们同时创建@Configuration bean和配置属性,则配置属性将胜出。它将覆盖@Configuration值。但是,如果您想将优先级更改为@Configuration,您可以将spring.cloud.openfeign.client.default-to-properties更改为false

如果我们想创建多个具有相同名称或URL的feign客户端,以便它们指向同一服务器但每个客户端都有不同的自定义配置,那么我们必须使用@FeignClientcontextId属性来避免这些配置bean的名称冲突。

@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
	//..
}
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
	//..
}

也可以配置FeignClient不继承父上下文中的bean。您可以通过覆盖FeignClientConfigurer bean中的inheritParentConfiguration()使其返回false来实现。

@Configuration
public class CustomConfiguration {
	@Bean
	public FeignClientConfigurer feignClientConfigurer() {
		return new FeignClientConfigurer() {
			@Override
			public boolean inheritParentConfiguration() {
				 return false;
			}
		};
	}
}
默认情况下,Feign客户端不编码斜杠/字符。您可以通过将spring.cloud.openfeign.client.decodeSlash的值设置为false来更改此行为。

SpringEncoder配置

在我们提供的SpringEncoder中,我们为二进制内容类型设置了null字符集,为所有其他内容类型设置了UTF-8

您可以通过将spring.cloud.openfeign.encoder.charset-from-content-type的值设置为true来修改此行为,以便从Content-Type标头字符集中派生字符集。

超时处理

我们可以同时配置默认客户端和命名客户端的超时。OpenFeign 使用两个超时参数

  • connectTimeout防止由于服务器处理时间过长而阻塞调用者。

  • readTimeout从连接建立时开始应用,当返回响应花费的时间过长时将被触发。

如果服务器未运行或不可用,数据包将导致连接被拒绝。通信将以错误消息或回退结束。如果connectTimeout设置得很低,这可能会在connectTimeout之前发生。执行查找和接收此类数据包所需的时间构成了此延迟的很大一部分。它会根据涉及DNS查找的远程主机而发生变化。

手动创建Feign客户端

在某些情况下,可能需要以使用上述方法无法实现的方式自定义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");
	}
}
在上面的示例中,FeignClientsConfiguration.class是Spring Cloud OpenFeign提供的默认配置。
PROD-SVC是客户端将向其发出请求的服务的名称。
Feign Contract对象定义了哪些注解和值在接口上有效。自动装配的Contract bean为SpringMVC注解提供支持,而不是默认的Feign原生注解。

您还可以使用Builder来配置FeignClient不继承父上下文中的bean。您可以通过在Builder上调用inheritParentContext(false)来实现。

Feign Spring Cloud断路器支持

如果Spring Cloud断路器位于类路径上并且spring.cloud.openfeign.circuitbreaker.enabled=true,则Feign将用断路器包装所有方法。

要禁用每个客户端的Spring Cloud断路器支持,请使用“prototype”作用域创建一个普通的Feign.Builder,例如:

@Configuration
public class FooConfiguration {
	@Bean
	@Scope("prototype")
	public Feign.Builder feignBuilder() {
		return Feign.builder();
	}
}

断路器名称遵循此模式<feignClientClassName>#<calledMethod>(<parameterTypes>)。当调用具有FooClient接口的@FeignClient且调用的接口方法没有参数为bar时,断路器名称将为FooClient#bar()

从2020.0.2开始,断路器名称模式已从<feignClientName>_<calledMethod>更改。使用2020.0.4中引入的CircuitBreakerNameResolver,断路器名称可以保留旧模式。

提供CircuitBreakerNameResolver的bean,您可以更改断路器名称模式。

@Configuration
public class FooConfiguration {
	@Bean
	public CircuitBreakerNameResolver circuitBreakerNameResolver() {
		return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
	}
}

要启用Spring Cloud断路器组,请将spring.cloud.openfeign.circuitbreaker.group.enabled属性设置为true(默认为false)。

使用配置属性配置断路器

您可以通过配置属性配置断路器。

例如,如果您有这个Feign客户端

@FeignClient(url = "https://127.0.0.1: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
如果您想切换回Spring Cloud 2022.0.0之前使用的断路器名称,可以将spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled设置为false

Feign Spring Cloud断路器回退

Spring Cloud断路器支持回退的概念:当断路器打开或发生错误时执行的默认代码路径。要为给定的@FeignClient启用回退,请将fallback属性设置为实现回退的类名。您还需要将您的实现声明为Spring bean。

@FeignClient(name = "test", url = "https://127.0.0.1:${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";
	}

}

如果需要访问导致回退触发的根本原因,可以在@FeignClient中使用fallbackFactory属性。

@FeignClient(name = "testClientWithFactory", url = "https://127.0.0.1:${server.port}/",
			fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {

	@GetMapping("/hello")
	Hello getHello();

	@GetMapping("/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和@Primary

当将Feign与Spring Cloud断路器回退一起使用时,ApplicationContext中存在多个相同类型的bean。这将导致@Autowired无法工作,因为没有完全一个bean,或一个标记为主要的bean。为了解决这个问题,Spring Cloud OpenFeign 将所有Feign实例标记为@Primary,以便Spring框架知道要注入哪个bean。在某些情况下,这可能并不理想。要关闭此行为,请将@FeignClientprimary属性设置为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 {

}
@FeignClient接口不应在服务器和客户端之间共享,并且不再支持使用类级别的@RequestMapping注解@FeignClient接口。

[[feign-request/response-compression]] === Feign请求/响应压缩

您可以考虑为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

这些属性允许您选择压缩媒体类型和最小请求阈值长度。

由于OkHttpClient使用“透明”压缩,即如果存在content-encodingaccept-encoding标头则禁用,因此当feign.okhttp.OkHttpClient存在于类路径上并且spring.cloud.openfeign.okhttp.enabled设置为true时,我们不会启用压缩。

Feign日志记录

为创建的每个Feign客户端创建一个日志记录器。默认情况下,日志记录器的名称是用于创建Feign客户端的接口的全限定类名。Feign日志记录仅响应DEBUG级别。

application.yml
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 的能力(Capabilities)公开了核心 Feign 组件,以便可以修改这些组件。例如,这些能力可以获取 Client,对其进行装饰,并将装饰后的实例返回给 Feign。对 Micrometer 的支持就是一个很好的实际例子。参见 Micrometer 支持

创建一到多个 Capability bean 并将它们放在 @FeignClient 配置中,您可以注册它们并修改相关客户端的行为。

@Configuration
public class FooConfiguration {
	@Bean
	Capability customCapability() {
		return new CustomCapability();
	}
}

Micrometer 支持

如果以下所有条件都为真,则会创建并注册一个 MicrometerObservationCapability bean,以便您的 Feign 客户端可由 Micrometer 监控。

  • feign-micrometer 位于类路径中

  • ObservationRegistry bean 可用

  • Feign Micrometer 属性设置为 true(默认值)

    • spring.cloud.openfeign.micrometer.enabled=true(对于所有客户端)

    • spring.cloud.openfeign.client.config.feignName.micrometer.enabled=true(对于单个客户端)

如果您的应用程序已使用 Micrometer,启用此功能就像将 feign-micrometer 放入您的类路径一样简单。

您也可以通过以下任一方式禁用此功能:

  • 从类路径中排除 feign-micrometer

  • 将某个 Feign Micrometer 属性设置为 false

    • spring.cloud.openfeign.micrometer.enabled=false

    • spring.cloud.openfeign.client.config.feignName.micrometer.enabled=false

spring.cloud.openfeign.micrometer.enabled=false 会禁用**所有**Feign 客户端的 Micrometer 支持,无论客户端级别标志 spring.cloud.openfeign.client.config.feignName.micrometer.enabled 的值如何。如果您想按客户端启用或禁用 Micrometer 支持,请不要设置 spring.cloud.openfeign.micrometer.enabled,而使用 spring.cloud.openfeign.client.config.feignName.micrometer.enabled

您还可以通过注册您自己的 bean 来自定义 MicrometerObservationCapability

@Configuration
public class FooConfiguration {
	@Bean
	public MicrometerObservationCapability micrometerObservationCapability(ObservationRegistry registry) {
		return new MicrometerObservationCapability(registry);
	}
}

仍然可以使用 MicrometerCapability 与 Feign 配合使用(仅限指标支持),您需要禁用 Micrometer 支持(spring.cloud.openfeign.micrometer.enabled=false)并创建一个 MicrometerCapability bean

@Configuration
public class FooConfiguration {
	@Bean
	public MicrometerCapability micrometerCapability(MeterRegistry meterRegistry) {
		return new MicrometerCapability(meterRegistry);
	}
}

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 禁用此功能。

Spring @RequestMapping 支持

Spring Cloud OpenFeign 支持 Spring 的 @RequestMapping 注解及其派生注解(例如 @GetMapping@PostMapping 等)的支持。@RequestMapping 注解上的属性(包括 valuemethodparamsheadersconsumesproduces)由 SpringMvcContract 解析为请求的内容。

考虑以下示例

定义一个使用 params 属性的接口。

@FeignClient("demo")
public interface DemoTemplate {

        @PostMapping(value = "/stores/{storeId}", params = "mode=upsert")
        Store update(@PathVariable("storeId") Long storeId, Store store);
}

在上面的示例中,请求 URL 解析为 /stores/storeId?mode=upsert
params 属性还支持使用多个 key=value 或仅一个 key

  • params = { "key1=v1", "key2=v2" } 时,请求 URL 解析为 /stores/storeId?key1=v1&key2=v2

  • params = "key" 时,请求 URL 解析为 /stores/storeId?key

Feign @QueryMap 支持

Spring Cloud OpenFeign 提供了一个等效的 @SpringQueryMap 注解,用于将 POJO 或 Map 参数作为查询参数映射进行注解。

例如,Params 类定义了参数 param1param2

// 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);
}

如果您需要更多地控制生成的查询参数映射,您可以实现自定义的 QueryMapEncoder bean。

HATEOAS 支持

Spring 提供了一些 API 来创建遵循 HATEOAS 原则的 REST 表示,Spring HateoasSpring Data REST

如果您的项目使用 org.springframework.boot:spring-boot-starter-hateoas 启动器或 org.springframework.boot:spring-boot-starter-data-rest 启动器,则默认情况下启用 Feign HATEOAS 支持。

启用 HATEOAS 支持后,Feign 客户端允许序列化和反序列化 HATEOAS 表示模型:EntityModelCollectionModelPagedModel

@FeignClient("demo")
public interface DemoTemplate {

	@GetMapping(path = "/stores")
	CollectionModel<Store> getStores();
}

Spring @MatrixVariable 支持

Spring Cloud OpenFeign 支持 Spring 的 @MatrixVariable 注解。

如果将映射作为方法参数传递,则 @MatrixVariable 路径段是通过使用 = 连接映射中的键值对来创建的。

如果传递不同的对象,则使用 =@MatrixVariable 注解中提供的 name(如果已定义)或带注解的变量名与提供的方法参数连接。

重要

尽管在服务器端,Spring 不要求用户将路径段占位符的名称与矩阵变量名称相同,因为这在客户端过于模棱两可,Spring Cloud OpenFeign 要求您添加一个名称与 @MatrixVariable 注解中提供的 name(如果已定义)或带注解的变量名匹配的路径段占位符。

例如

@GetMapping("/objects/links/{matrixVars}")
Map<String, List<String>> getObjects(@MatrixVariable Map<String, List<String>> matrixVars);

请注意,变量名和路径段占位符都称为 matrixVars

@FeignClient("demo")
public interface DemoTemplate {

	@GetMapping(path = "/stores")
	CollectionModel<Store> getStores();
}

Feign CollectionFormat 支持

我们通过提供 @CollectionFormat 注解来支持 feign.CollectionFormat。您可以使用它通过传递所需的 feign.CollectionFormat 作为注解值来注解 Feign 客户端方法(或整个类以影响所有方法)。

在下面的示例中,使用 CSV 格式而不是默认的 EXPLODED 格式来处理方法。

@FeignClient(name = "demo")
protected interface DemoFeignClient {

    @CollectionFormat(feign.CollectionFormat.CSV)
    @GetMapping(path = "/test")
    ResponseEntity performRequest(String test);

}

响应式支持

由于 OpenFeign 项目 目前不支持响应式客户端,例如 Spring WebClient,Spring Cloud OpenFeign 也不支持。

由于 Spring Cloud OpenFeign 项目现在被认为是功能完整的,即使它在上游项目中可用,我们也不打算添加支持。我们建议改为迁移到 Spring 接口客户端。它同时支持阻塞和响应式堆栈。

在此之前,我们建议使用 feign-reactive 来支持 Spring WebClient。

早期初始化错误

我们不建议在应用程序生命周期的早期阶段使用 Feign 客户端,此时正在处理配置和初始化 bean。在 bean 初始化期间使用客户端不受支持。

同样,根据您使用 Feign 客户端的方式,您可能会在启动应用程序时看到初始化错误。要解决此问题,您可以在自动装配客户端时使用 ObjectProvider

@Autowired
ObjectProvider<TestFeignClient> testFeignClient;

Spring Data 支持

如果类路径中存在 Jackson Databind 和 Spring Data Commons,则会自动添加 org.springframework.data.domain.Pageorg.springframework.data.domain.Sort 的转换器。

要禁用此行为,请设置

spring.cloud.openfeign.autoconfiguration.jackson.enabled=false

详情请参见 org.springframework.cloud.openfeign.FeignAutoConfiguration.FeignJacksonConfiguration

Spring @RefreshScope 支持

如果启用 Feign 客户端刷新,则每个 Feign 客户端都将使用

  • feign.Request.Options 作为刷新范围的 bean 创建。这意味着诸如 connectTimeoutreadTimeout 之类的属性可以针对任何 Feign 客户端实例进行刷新。

  • 一个包装在 org.springframework.cloud.openfeign.RefreshableUrl 下的 URL。这意味着如果使用 spring.cloud.openfeign.client.config.{feignName}.url 属性定义了 Feign 客户端的 URL,则可以针对任何 Feign 客户端实例刷新该 URL。

您可以通过 POST /actuator/refresh 刷新这些属性。

默认情况下,Feign 客户端中的刷新行为被禁用。使用以下属性启用刷新行为

spring.cloud.openfeign.client.refresh-enabled=true
请勿使用 @RefreshScope 注解对 @FeignClient 接口进行注解。

OAuth2 支持

可以通过将 spring-boot-starter-oauth2-client 依赖项添加到您的项目并设置以下标志来启用 OAuth2 支持

spring.cloud.openfeign.oauth2.enabled=true

当标志设置为 true 且存在 oauth2 客户端上下文资源详细信息时,将创建一个 OAuth2AccessTokenInterceptor 类的 bean。在每次请求之前,拦截器都会解析所需的访问令牌并将其作为标头包含在内。OAuth2AccessTokenInterceptor 使用 OAuth2AuthorizedClientManager 获取包含 OAuth2AccessTokenOAuth2AuthorizedClient。如果用户使用 spring.cloud.openfeign.oauth2.clientRegistrationId 属性指定了 OAuth2 clientRegistrationId,则它将用于检索令牌。如果没有检索到令牌或未指定 clientRegistrationId,则将使用从 url 主机段检索到的 serviceId

提示

使用 serviceId 作为 OAuth2 clientRegistrationId 对于负载均衡的 Feign 客户端很方便。对于非负载均衡的客户端,基于属性的 clientRegistrationId 是一种合适的方法。

提示

如果您不想使用 OAuth2AuthorizedClientManager 的默认设置,您只需在您的配置中实例化此类型的 bean。

转换负载均衡的 HTTP 请求

您可以使用选定的 ServiceInstance 来转换负载均衡的 HTTP 请求。

对于 Request,您需要实现并定义 LoadBalancerFeignRequestTransformer,如下所示

@Bean
public LoadBalancerFeignRequestTransformer transformer() {
	return new LoadBalancerFeignRequestTransformer() {

		@Override
		public Request transformRequest(Request request, ServiceInstance instance) {
			Map<String, Collection<String>> headers = new HashMap<>(request.headers());
			headers.put("X-ServiceId", Collections.singletonList(instance.getServiceId()));
			headers.put("X-InstanceId", Collections.singletonList(instance.getInstanceId()));
			return Request.create(request.httpMethod(), request.url(), headers, request.body(), request.charset(),
					request.requestTemplate());
		}
	};
}

如果定义了多个转换器,则按 bean 定义的顺序应用它们。或者,您可以使用 LoadBalancerFeignRequestTransformer.DEFAULT_ORDER 来指定顺序。

X-Forwarded 标头支持

可以通过设置以下标志来启用 X-Forwarded-HostX-Forwarded-Proto 支持

spring.cloud.loadbalancer.x-forwarded.enabled=true

向 Feign 客户端提供 URL 的支持方式

您可以通过以下任何方式向 Feign 客户端提供 URL

情况 示例 详情

URL 在 @FeignClient 注解中提供。

@FeignClient(name="testClient", url="https://127.0.0.1:8081")

URL 从注解的 url 属性解析,不进行负载均衡。

URL 在 @FeignClient 注解和配置属性中提供。

@FeignClient(name="testClient", url="https://127.0.0.1:8081") 和在 application.yml 中定义的属性 spring.cloud.openfeign.client.config.testClient.url=https://127.0.0.1:8081

URL 从注解的 url 属性解析,不进行负载均衡。配置属性中提供的 URL 保持未使用。

URL 未在 @FeignClient 注解中提供,但在配置属性中提供。

@FeignClient(name="testClient") 和在 application.yml 中定义的属性 spring.cloud.openfeign.client.config.testClient.url=https://127.0.0.1:8081

URL 从配置属性解析,不进行负载均衡。如果 spring.cloud.openfeign.client.refresh-enabled=true,则配置属性中定义的 URL 可以根据Spring RefreshScope 支持中的描述进行刷新。

URL 既未在 @FeignClient 注解中提供,也未在配置属性中提供。

@FeignClient(name="testClient")

URL 从注解的 name 属性解析,并进行负载均衡。

AOT 和原生镜像支持

Spring Cloud OpenFeign 支持 Spring AOT 转换和原生镜像,但是,只有在禁用刷新模式、禁用 Feign 客户端刷新(默认设置)和禁用延迟 @FeignClient 属性解析(默认设置)的情况下才支持。

如果要以 AOT 或原生镜像模式运行 Spring Cloud OpenFeign 客户端,请确保将 spring.cloud.refresh.enabled 设置为 false
如果要以 AOT 或原生镜像模式运行 Spring Cloud OpenFeign 客户端,请确保未将 spring.cloud.openfeign.client.refresh-enabled 设置为 true
如果要以 AOT 或原生镜像模式运行 Spring Cloud OpenFeign 客户端,请确保未将 spring.cloud.openfeign.lazy-attributes-resolution 设置为 true
但是,如果通过属性设置 url 值,则可以通过使用 -Dspring.cloud.openfeign.client.config.[clientId].url=[url] 标志运行镜像来覆盖 @FeignClienturl 值。为了启用覆盖,也必须通过属性设置 url 值,而不能在构建时通过 @FeignClient 属性设置。

配置属性

要查看所有与 Spring Cloud OpenFeign 相关的配置属性列表,请查看附录页面