OpenFeign使用详解

什么是OpenFeign?

OpenFeign 是一个声明式的 HTTP 客户端,旨在简化微服务架构中不同服务之间的 HTTP 调用。它通过集成 Ribbon 实现了客户端负载均衡,并且能够与 Eureka、Consul 等服务发现组件无缝对接。使用 OpenFeign,开发者只需定义接口并使用注解来配置 HTTP 请求,从而避免了编写大量的模板代码。

cloud官网介绍Feign:Spring Cloud OpenFeign

OpenFeign源码:GitHub - OpenFeign/feign: Feign makes writing java http clients easier

Feign 的实现

Feign 在 Ribbon + RestTemplate 的基础上进行了进一步封装,帮助开发者定义和实现依赖服务的接口。通过 Feign,开发者只需创建一个接口并使用注解进行配置,即可完成对服务提供方的接口绑定,简化了使用 Spring Cloud Ribbon 时自动封装服务调用客户端的开发工作。

Feign 和 OpenFeign 的区别

特性FeignOpenFeign
依赖<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>    
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
作用轻量级 RESTful HTTP 服务客户端,内置 Ribbon 用于客户端负载均衡,调用服务注册中心的服务。在 Feign 的基础上支持 SpringMVC 的注解(如 @RequestMapping),通过动态代理生成实现类,实现负载均衡并调用其他服务。

OpenFeign 的优点

  1. 声明式调用:通过注解定义 HTTP 请求,代码更加简洁和易读。

  2. 集成 Ribbon:支持客户端负载均衡,确保请求在多个服务实例之间均匀分布。

  3. 支持服务发现:与 Eureka、Consul 等服务发现组件无缝集成,自动发现和调用服务。

  4. 支持熔断:与 Hystrix、Sentinel 等熔断器集成,提升系统的稳定性和容错能力。

  5. 可扩展性:支持自定义拦截器、编码器和解码器,便于根据需求进行扩展和定制化。

基本用法

父项目的 pom.xml 文件定义了所有子模块共享的依赖和插件。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cloud</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging> <!-- 这里设置为pom -->
    <name>demo</name>
    <description>demo</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.2</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2023.0.1.0</version>
            <type>pom</type>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <!-- 定义子模块 -->
    <modules>
        <module>service1</module>
        <module>service2</module>
        <module>common</module>
    </modules>

</project>

子项目 common

common 是一个简单的 Spring Boot 服务,提供一个 REST API。

ServiceBController.java

package com.example.common.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ServiceBController {

    @GetMapping("/api/resource")
    public String getResource() {
        return "来自服务c的问候!";
    }
}

子项目 service-a

service1 使用 OpenFeign 调用 service-b 的服务。

Service1Application.java

在启动类,加上 @EnableFeignClients 注解。

package cloud.service1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class Service1Application {

	public static void main(String[] args) {
		SpringApplication.run(Service1Application.class, args);
	}

}

ServiceBClient.java

package cloud.service1.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "service-b", url = "http://localhost:8080")
public interface ServiceBClient {

    @GetMapping("/api/resource")
    String getResource();
}

ServiceAController.java 

package cloud.service1.controller;


import cloud.service1.client.ServiceBClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ServiceAController {

    private final ServiceBClient serviceBClient;

    public ServiceAController(ServiceBClient serviceBClient) {
        this.serviceBClient = serviceBClient;
    }

    @GetMapping("/api/resource")
    public String getResource() {
        return serviceBClient.getResource();
    }
}

如图所示,证明已经访问成功了。

@FeignClient 标签的常用属性 

@FeignClient 注解是 Spring Cloud OpenFeign 中用于声明一个 Feign 客户端的核心注解。它提供了丰富的属性来配置 Feign 客户端的行为,以满足不同场景下的微服务调用需求。

1、name

  • 类型: String
  • 描述: 指定 Feign 客户端调用的服务名称。这是一个必填属性,通常是注册在服务发现(如 Nacos)中的服务名。
@FeignClient(name = "demo-user")
public interface UserClient {
    // ...
}

2、url

  • 类型: String
  • 描述: 指定服务的 URL,通常用于调试或服务未注册到服务发现时。url 属性会覆盖 name 属性。
@FeignClient(name = "demo-user", url = "http://localhost:8080")
public interface UserClient {
    // ...
}

3、configuration

  • 类型: Class<?>[]
  • 描述: 指定自定义配置类,配置类可以用来定制 Feign 客户端的行为,如请求拦截器、编码器和解码器等。
@FeignClient(name = "demo-user", configuration = FeignConfig.class)
public interface UserClient {
    // ...
}

配置类示例:

@Configuration
public class FeignConfig {
    @Bean
    public RequestInterceptor requestInterceptor() {
        return template -> template.header("Custom-Header", "CustomHeaderValue");
    }
}

4、fallback

  • 类型: Class<?>
  • 描述: 指定服务降级的实现类。当 Feign 客户端调用失败时,会调用 fallback 指定的类中的方法。
@FeignClient(name = "demo-user", fallback = UserClientFallback.class)
public interface UserClient {
    // ...
}

5、fallbackFactory

  • 类型: Class<?>
  • 描述: 指定服务降级的工厂类,该工厂类可以提供更多的上下文信息,例如异常信息。
@FeignClient(name = "demo-user", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
    // ...
}

6、path

  • 类型: String
  • 描述: 指定服务的统一前缀路径,在定义 Feign 接口的方法时可以省略该路径。
@FeignClient(name = "demo-user", path = "/api/users")
public interface UserClient {
    @GetMapping("/{id}")
    User getUserById(@PathVariable("id") Long id);
}

7、decode404

  • 类型: boolean
  • 描述: 指定是否将 HTTP 404 响应解码为 Feign 客户端的 fallback,默认值为 false。
@FeignClient(name = "demo-user", decode404 = true)
public interface UserClient {
    // ...
}

8、primary

  • 类型: boolean
  • 描述: 指定该 Feign 客户端是否为主要的 @Primary Bean,这对某些场景下的自动装配很有用,默认值为 true。
@FeignClient(name = "demo-user", primary = false)
public interface UserClient {
    // ...
}

9、contextId

  • 类型: String
  • 描述: 用于在多 Feign 客户端实例中区分不同的上下文 ID。特别适用于多个 Feign 客户端指向同一服务时的配置。
@FeignClient(name = "demo-user", contextId = "userClient1")
public interface UserClient1 {
    // ...
}

@FeignClient(name = "demo-user", contextId = "userClient2")
public interface UserClient2 {
    // ...
}

添加请求头信息

在 Spring Cloud OpenFeign 中,可以通过多种方式添加请求头信息。以下是三种常见的方法:

1. 在方法参数上添加请求头信息

可以在 Feign 客户端接口的方法参数上使用 @RequestHeader 注解来添加请求头信息。

@FeignClient(name = "demo-user")
public interface UserClient {

    @GetMapping("/api/users/{id}")
    User getUserById(@PathVariable("id") Long id, @RequestHeader("Custom-Header") String customHeader);
}

2. 使用 Feign 配置类定义请求拦截器

如果需要在所有请求中添加相同的请求头,可以通过定义一个 Feign 请求拦截器来实现。

定义 Feign 配置类:

@Configuration
public class FeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            requestTemplate.header("Custom-Header", "CustomHeaderValue");
            requestTemplate.header("Another-Header", "AnotherHeaderValue");
        };
    }
}

在 Feign 客户端中使用配置类: 

@FeignClient(name = "demo-user", configuration = FeignConfig.class)
public interface UserClient {

    @GetMapping("/api/users/{id}")
    User getUserById(@PathVariable("id") Long id);
}

3. 动态添加请求头信息

如果需要根据某些条件动态添加请求头信息,可以在拦截器中编写逻辑。

定义 Feign 配置类:

@Configuration
public class FeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            // 动态添加请求头信息
            String customHeaderValue = getCustomHeaderValue();
            requestTemplate.header("Custom-Header", customHeaderValue);
        };
    }

    private String getCustomHeaderValue() {
        // 根据某些条件动态生成请求头值
        return "DynamicHeaderValue";
    }
}

在 Feign 客户端中使用配置类:

@FeignClient(name = "demo-user", configuration = FeignConfig.class)
public interface UserClient {

    @GetMapping("/api/users/{id}")
    User getUserById(@PathVariable("id") Long id);
}

超时控制

在 Spring Cloud OpenFeign 中,超时控制是非常重要的,特别是在微服务架构中,确保服务之间的调用不会因为超时而导致整个系统的不稳定。OpenFeign 提供了两种超时参数:connectTimeout 和 readTimeout,分别用于控制连接超时和读取超时。

第一步:提供方接口,制造超时场景

首先,我们需要在提供方接口中制造一个超时场景,以便在消费方调用时能够触发超时。

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) throws InterruptedException {
        // 模拟超时场景
        Thread.sleep(5000); // 休眠 5 秒
        return new User(id, "John Doe");
    }
}

第二步:消费方接口调用

在消费方接口中调用提供方接口,并配置超时参数。

@FeignClient(name = "demo-user", url = "http://localhost:8080")
public interface UserClient {

    @GetMapping("/api/users/{id}")
    User getUserById(@PathVariable("id") Long id);
}

第三步:超时处理

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

  • connectTimeout:防止因服务器处理时间过长而阻塞调用者。
  • readTimeout:从连接建立时开始应用,当返回响应的时间过长时就会被触发。

配置超时参数

可以通过配置文件(如 application.yml 或 application.properties)来设置超时参数。

示例:application.yml

feign:
  client:
    config:
      default:
        connectTimeout: 2000  # 连接超时时间,单位为毫秒
        readTimeout: 3000     # 读取超时时间,单位为毫秒

示例:application.properties

feign.client.config.default.connectTimeout=2000
feign.client.config.default.readTimeout=3000

手动创建 Feign Client

在 Spring Cloud OpenFeign 中,通常使用 @FeignClient 注解来声明一个 Feign 客户端。然而,有时可能需要手动创建 Feign 客户端,例如在某些特殊场景下,或者需要更细粒度的控制。

示例代码

定义 Feign 接口

首先,定义一个标准的 Feign 接口。

public interface FooClient {

    @GetMapping("/api/foo")
    String getFoo();
}

手动创建 Feign 客户端

使用 Feign Builder API 手动创建两个 Feign 客户端实例,并为每个客户端配置不同的请求拦截器。

import feign.Feign;
import feign.auth.BasicAuthRequestInterceptor;
import feign.Client;
import feign.Contract;
import feign.Encoder;
import feign.Decoder;
import feign.micrometer.MicrometerObservationCapability;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RestController;

// 导入 Spring Cloud OpenFeign 提供的默认配置
@Import(FeignClientsConfiguration.class)
@RestController
public class FooController {

    // 定义两个 Feign 客户端实例
    private FooClient fooClient;
    private FooClient adminClient;

    // 自动注入 Feign 所需的组件
    @Autowired
    public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) {
        // 创建第一个 Feign 客户端实例,配置用户认证
        this.fooClient = Feign.builder()
                .client(client) // 设置客户端
                .encoder(encoder) // 设置编码器
                .decoder(decoder) // 设置解码器
                .contract(contract) // 设置契约(注解解析器)
                .addCapability(micrometerObservationCapability) // 添加 Micrometer 观测能力
                .requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) // 设置请求拦截器,使用用户认证
                .target(FooClient.class, "https://PROD-SVC"); // 指定目标服务 URL

        // 创建第二个 Feign 客户端实例,配置管理员认证
        this.adminClient = Feign.builder()
                .client(client) // 设置客户端
                .encoder(encoder) // 设置编码器
                .decoder(decoder) // 设置解码器
                .contract(contract) // 设置契约(注解解析器)
                .addCapability(micrometerObservationCapability) // 添加 Micrometer 观测能力
                .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) // 设置请求拦截器,使用管理员认证
                .target(FooClient.class, "https://PROD-SVC"); // 指定目标服务 URL
    }

    // 示例方法,使用 fooClient 和 adminClient
    public void exampleMethod() {
        String fooResponse = fooClient.getFoo(); // 调用用户认证的 Feign 客户端
        String adminResponse = adminClient.getFoo(); // 调用管理员认证的 Feign 客户端
        System.out.println("Foo Response: " + fooResponse);
        System.out.println("Admin Response: " + adminResponse);
    }
}

Feign Spring Cloud CircuitBreaker 的支持

Spring Cloud OpenFeign 提供了对 Spring Cloud CircuitBreaker 的支持,使得在 Feign 客户端中可以轻松集成熔断器(Circuit Breaker)功能。通过配置,可以全局启用或禁用 CircuitBreaker 支持,并且可以自定义 CircuitBreaker 的名称模式。

启用 Spring Cloud CircuitBreaker 支持

如果 Spring Cloud CircuitBreaker 在 classpath 上,并且 spring.cloud.openfeign.circuitbreaker.enabled=true,Feign 将用 CircuitBreaker 来包装所有方法。

示例配置

spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true

禁用特定客户端的 CircuitBreaker 支持

为了在每个客户端的基础上禁用 Spring Cloud CircuitBreaker 的支持,可以创建一个具有 "prototype" scope 的 Feign.Builder。

import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class FooConfiguration {

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

CircuitBreaker 名称模式

CircuitBreaker 的名称遵循这种模式 <feignClientClassName>#<calledMethod>(<parameterTypes>)。例如,当调用一个带有 FooClient 接口的 @FeignClient,并且被调用的接口方法 bar 没有参数,那么 CircuitBreaker 的名称将是 FooClient#bar()。

自定义 CircuitBreaker 名称模式

从 2020.0.2 开始,CircuitBreaker 名称模式已经从 <feignClientName>_<calledMethod> 改变。使用 2020.0.4 中引入的 CircuitBreakerNameResolver,可以保留旧的命名模式。

import org.springframework.cloud.client.circuitbreaker.CircuitBreakerNameResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;

@Configuration
public class FooConfiguration {

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

启用 Spring Cloud CircuitBreaker Group

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

spring:
  cloud:
    openfeign:
      circuitbreaker:
        group:
          enabled: true

使用配置属性配置 CircuitBreaker

在 Spring Cloud OpenFeign 中,可以通过配置属性来配置 CircuitBreaker。

定义 Feign 客户端

首先,定义一个 Feign 客户端接口。

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

// 使用 @FeignClient 注解定义 Feign 客户端
@FeignClient(url = "http://localhost:8080")
public interface DemoClient {

    // 定义一个 GET 请求方法
    @GetMapping("/demo")
    String getDemo();
}

配置 CircuitBreaker

通过配置属性来配置 CircuitBreaker。

spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true  # 启用 CircuitBreaker
        alphanumeric-ids:
          enabled: true  # 启用字母数字 ID

resilience4j:
  circuitbreaker:
    instances:
      DemoClientgetDemo:  # CircuitBreaker 实例名称
        minimumNumberOfCalls: 69  # 最小调用次数
  timelimiter:
    instances:
      DemoClientgetDemo:  # 时间限制器实例名称
        timeoutDuration: 10s  # 超时时间

如果你想切换回 Spring Cloud 2022.0.0 之前使用的 CircuitBreaker 名称模式,可以将 spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled 设置为 false。

spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true
        alphanumeric-ids:
          enabled: false  # 禁用字母数字 ID,使用旧的命名模式

Feign Spring Cloud CircuitBreaker Fallback

Spring Cloud CircuitBreaker 支持 fallback 的概念:一个默认的代码路径,在 circuit 打开或出现错误时执行。要为一个给定的 @FeignClient 启用 fallback,可以将 fallback 属性设置为实现 fallback 的类名。我们还需要将我们的实现声明为一个 Spring Bean。

定义 Feign 客户端

定义一个 Feign 客户端接口,并使用 @FeignClient 注解,设置 fallback 属性。

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.stereotype.Component;

@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();
}

定义 Fallback 类

定义一个 Fallback 类,实现 Feign 客户端接口,并在熔断器打开或出现错误时提供降级响应。

import org.springframework.stereotype.Component;
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;

@Component
static class Fallback implements TestClient {

    @Override
    public Hello getHello() {
        throw new NoFallbackAvailableException("Boom!", new RuntimeException());
    }

    @Override
    public String getException() {
        return "Fixed response";
    }
}

使用 FallbackFactory 访问触发原因

如果需要访问使 fallback 触发的原因,可以使用 @FeignClient 里面的 fallbackFactory 属性。

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.stereotype.Component;
import org.springframework.cloud.client.circuitbreaker.FallbackFactory;

@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallbackFactory = TestClientFallbackFactory.class)
protected interface TestClient {

    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello getHello();

    @RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
    String getException();
}

@Component
static class TestClientFallbackFactory implements FallbackFactory<TestClient> {

    @Override
    public TestClient create(Throwable cause) {
        return new TestClient() {
            @Override
            public Hello getHello() {
                System.out.println("Fallback cause: " + cause);
                throw new NoFallbackAvailableException("Boom!", cause);
            }

            @Override
            public String getException() {
                System.out.println("Fallback cause: " + cause);
                return "Fixed response";
            }
        };
    }
}

Feign 和 @Primary

当使用 Feign 与 Spring Cloud CircuitBreaker fallback 时,ApplicationContext 中可能存在多个相同类型的 Bean。这将导致 @Autowired 不起作用,因为没有确切的一个 Bean,或一个被标记为 @Primary 的 Bean。为了解决这个问题,Spring Cloud OpenFeign 将所有 Feign 实例标记为 @Primary,因此 Spring Framework 将知道要注入哪个 Bean。在某些情况下,这可能是不可取的。要关闭这种行为,将 @FeignClient 的 primary 属性设置为 false。

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@FeignClient(name = "test", url = "http://localhost:${server.port}/", primary = false)
public interface TestClient {

    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    String getHello();
}

Feign 继承的支持

Feign 支持接口继承,这意味着你可以定义一个通用的接口,然后在多个 Feign 客户端中继承和重用这个接口。通过接口继承,可以减少代码重复,提高代码的可维护性和可读性。

定义通用接口

首先,定义一个通用的接口,包含一些通用的方法。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

public interface CommonApi {

    @GetMapping("/common/{id}")
    String getCommon(@PathVariable("id") Long id);
}

定义 Feign 客户端接口

定义一个 Feign 客户端接口,继承通用接口,并添加一些特定于该客户端的方法。

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "demo-service", url = "http://localhost:8080")
public interface DemoClient extends CommonApi {

    @GetMapping("/demo/{id}")
    String getDemo(@PathVariable("id") Long id);
}

使用 Feign 客户端

在需要使用 Feign 客户端的地方,注入并调用接口方法。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @Autowired
    private DemoClient demoClient;

    @GetMapping("/demo/{id}")
    public String getDemo(@PathVariable Long id) {
        return demoClient.getDemo(id);
    }

    @GetMapping("/common/{id}")
    public String getCommon(@PathVariable Long id) {
        return demoClient.getCommon(id);
    }
}

Feign request/response 压缩

在微服务架构中,网络传输的数据量可能会非常大,尤其是在处理大量数据或频繁调用远程服务时。为了减少网络传输的开销,可以对 Feign 的请求和响应进行压缩。Feign 支持通过配置启用 Gzip 压缩,从而减少数据传输的大小,提高性能。

在配置文件中启用 Feign 的请求和响应压缩。

application.yml

spring:
  cloud:
    openfeign:
      compression:
        request:
          enabled: true  # 启用请求压缩
          mime-types: text/xml,application/xml,application/json  # 指定压缩的 MIME 类型
          min-request-size: 2048  # 指定最小请求大小(字节)
        response:
          enabled: true  # 启用响应压缩

application.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
spring.cloud.openfeign.compression.response.enabled=true

Feign 日志

每个创建的 Feign 客户端都会创建一个 logger。默认情况下,logger 的名字是用于创建 Feign 客户端的接口的全类名称。Feign 的日志只响应 DEBUG 级别。

配置 Feign 日志

1、配置日志级别

在 application.yml 或 application.properties 中配置 Feign 客户端的日志级别。

application.yml

logging:
  level:
    project.user.UserClient: DEBUG

application.properties

logging.level.project.user.UserClient=DEBUG

配置 Logger.Level

可以为每个客户端配置 Logger.Level 对象,告诉 Feign 要记录多少内容。选择是:

  • NONE: 没日志(默认)。
  • BASIC: 只记录请求方法和 URL 以及响应状态代码和执行时间。
  • HEADERS: 记录基本信息以及请求和响应头。
  • FULL: 记录请求和响应的 header、正文和元数据。
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

Micrometer 的支持

Spring Cloud OpenFeign 提供了对 Micrometer 的支持,使得 Feign 客户端的调用可以被 Micrometer 观察到。通过启用 Micrometer 支持,可以收集 Feign 客户端的调用指标,并将其集成到 Micrometer 的监控系统中。

配置 Micrometer 支持

在配置文件中启用 Micrometer 支持。

application.yml

spring:
  cloud:
    openfeign:
      micrometer:
        enabled: true  # 启用 Micrometer 支持

application.properties

spring.cloud.openfeign.micrometer.enabled=true

禁用 Micrometer 支持

可以通过以下两种方式禁用 Micrometer 支持:

  • 从 classpath 中排除 feign-micrometer。
  • 将 spring.cloud.openfeign.micrometer.enabled 设置为 false。

application.yml

spring:
  cloud:
    openfeign:
      micrometer:
        enabled: false  # 禁用 Micrometer 支持

application.properties

spring.cloud.openfeign.micrometer.enabled=false

自定义 MicrometerObservationCapability

可以通过注册你自己的 MicrometerObservationCapability Bean 来自定义 Micrometer 支持。

import io.micrometer.observation.ObservationRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.openfeign.MicrometerObservationCapability;

@Configuration
public class FooConfiguration {

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

使用 MicrometerCapability

仍然可以在 Feign 中使用 MicrometerCapability(仅支持指标),你需要禁用 Micrometer 支持(spring.cloud.openfeign.micrometer.enabled=false)并创建一个 MicrometerCapability Bean。

import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.openfeign.MicrometerCapability;

@Configuration
public class FooConfiguration {

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

Feign 缓存

Spring Cloud OpenFeign 提供了对 Spring 缓存的支持,使得 Feign 客户端可以识别其接口上的 @Cache* 注解。通过启用缓存支持,可以减少对远程服务的调用次数,提高性能。

启用 Feign 缓存

在 Spring Boot 应用的主类上启用缓存。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
@EnableCaching
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

定义一个 Feign 客户端接口,并使用 @Cacheable 注解。 

import org.springframework.cache.annotation.Cacheable;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "demo-service", url = "http://localhost:8080")
public interface DemoClient {

    @GetMapping("/demo/{filterParam}")
    @Cacheable(cacheNames = "demo-cache", key = "#keyParam")
    String demoEndpoint(String keyParam, @PathVariable String filterParam);
}

禁用 Feign 缓存

可以通过属性 spring.cloud.openfeign.cache.enabled=false 来禁用 Feign 缓存功能。

spring:
  cloud:
    openfeign:
      cache:
        enabled: false  # 禁用 Feign 缓存

Feign @QueryMap 的支持

Spring Cloud OpenFeign 提供了一个等价的 @SpringQueryMap 注解,用于将 POJO 或 Map 参数注解为查询参数 map。通过使用 @SpringQueryMap 注解,可以更方便地将复杂对象转换为查询参数,而不需要手动拼接查询字符串。

1. 定义 POJO 类

首先,定义一个 POJO 类,用于表示查询参数。

public class QueryParams {
    private String param1;
    private String param2;

    // Getters and Setters
}

2. 定义 Feign 客户端接口

定义一个 Feign 客户端接口,并使用 @SpringQueryMap 注解将 POJO 或 Map 参数注解为查询参数 map。

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "demo-service", url = "http://localhost:8080")
public interface DemoClient {

    @GetMapping("/demo")
    String getDemo(@SpringQueryMap QueryParams queryParams);
}

3. 使用 Feign 客户端

在需要使用 Feign 客户端的地方,注入并调用接口方法。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @Autowired
    private DemoClient demoClient;

    @GetMapping("/demo")
    public String getDemo() {
        QueryParams queryParams = new QueryParams();
        queryParams.setParam1("value1");
        queryParams.setParam2("value2");
        return demoClient.getDemo(queryParams);
    }
}

HATEOAS 的支持

Spring 提供了一些 API 来创建遵循 HATEOAS 原则的 REST 表示,如 Spring Hateoas 和 Spring Data REST。如果你的项目使用了 org.springframework.boot:spring-boot-starter-hateoas 或 org.springframework.boot:spring-boot-starter-data-rest starter,Feign HATEOAS 支持会被默认启用。

当HATEOAS支持被启用时,Feign 客户端被允许序列化和反序列化 HATEOAS 表示模型: EntityModel、 CollectionModel 和 PagedModel.。

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "demo-service", url = "http://localhost:8080")
public interface DemoClient {

    @GetMapping("/demo/{id}")
    EntityModel<Demo> getDemo(@PathVariable("id") Long id);

    @GetMapping("/demos")
    CollectionModel<EntityModel<Demo>> getAllDemos();
}

OAuth2 的支持

Spring Cloud OpenFeign 提供了对 OAuth2 的支持,使得 Feign 客户端可以自动获取并附加 OAuth2 访问令牌到请求头中。通过启用 OAuth2 支持,可以简化与 OAuth2 保护的资源的交互。

 在配置文件中启用 OAuth2 支持,并指定 OAuth2 客户端的注册 ID。

spring:
  cloud:
    openfeign:
      oauth2:
        enabled: true  # 启用 OAuth2 支持
        clientRegistrationId: my-client-registration-id  # 指定 OAuth2 客户端的注册 ID

转换负载均衡的 HTTP 请求

在 Spring Cloud OpenFeign 中,你可以使用选定的 ServiceInstance 来转换负载均衡的 HTTP 请求。为了实现这一点,你需要实现和定义 LoadBalancerFeignRequestTransformer,该接口允许你在请求发送到目标服务之前对其进行转换。

实现 LoadBalancerFeignRequestTransformer

1. 实现 LoadBalancerFeignRequestTransformer

首先,实现 LoadBalancerFeignRequestTransformer 接口,并在 transformRequest 方法中定义请求转换逻辑。

 

import feign.Request;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerFeignRequestTransformer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class FeignConfig {

    // 定义一个 LoadBalancerFeignRequestTransformer Bean
    @Bean
    public LoadBalancerFeignRequestTransformer transformer() {
        return new LoadBalancerFeignRequestTransformer() {

            // 实现 transformRequest 方法,定义请求转换逻辑
            @Override
            public Request transformRequest(Request request, ServiceInstance instance) {
                // 创建一个新的请求头 Map,并复制原始请求的请求头
                Map<String, Collection<String>> headers = new HashMap<>(request.headers());
                
                // 添加自定义请求头,包含服务 ID
                headers.put("X-ServiceId", Collections.singletonList(instance.getServiceId()));
                
                // 添加自定义请求头,包含实例 ID
                headers.put("X-InstanceId", Collections.singletonList(instance.getInstanceId()));
                
                // 创建并返回一个新的 Request 对象,包含转换后的请求头
                return Request.create(request.httpMethod(), request.url(), headers, request.body(), request.charset(),
                        request.requestTemplate());
            }
        };
    }
}

2. 配置 Feign 客户端

在 Feign 客户端配置中,启用负载均衡并使用自定义的 LoadBalancerFeignRequestTransformer。 

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// 定义一个 Feign 客户端接口,启用负载均衡
@FeignClient(name = "demo-service")
public interface DemoClient {

    // 定义一个 GET 请求方法
    @GetMapping("/demo/{id}")
    String getDemo(@PathVariable("id") Long id);
}

参考文章

常见的应用属性

Spring Cloud OpenFeign 中文文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无限循环者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值