1. 概述

在本教程中,我们将介绍 Spring Cloud OpenFeign —— 一个用于 Spring Boot 应用程序的声明式 REST 客户端。

Feign 提供了可插拔的注解支持,使得编写 Web 服务客户端变得更加简单,支持 Feign 注解和 JAX-RS 注解。

此外,Spring Cloud 还支持 Spring MVC 注解,并允许使用与 Spring Web 中相同的 HttpMessageConverters

使用 Feign 的一个显著优势是:我们无需编写任何调用服务的代码,只需要定义一个接口即可。

2. 依赖配置

首先,我们创建一个 Spring Boot Web 项目,并在 pom.xml 文件中添加 spring-cloud-starter-openfeign 依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

同时,还需要添加 spring-cloud-dependencies

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

可以在 Maven Central 上找到 spring-cloud-starter-openfeignspring-cloud-dependencies 的最新版本。

3. Feign 客户端

接下来,我们需要在主类上添加 @EnableFeignClients 注解:

@SpringBootApplication
@EnableFeignClients
public class ExampleApplication {

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

通过这个注解,Spring Boot 会自动扫描标记为 Feign 客户端的接口。

然后,使用 @FeignClient 注解声明一个 Feign 客户端

@FeignClient(value = "jplaceholder", url = "https://jsonplaceholder.typicode.com/")
public interface JSONPlaceHolderClient {

    @RequestMapping(method = RequestMethod.GET, value = "/posts")
    List<Post> getPosts();

    @RequestMapping(method = RequestMethod.GET, value = "/posts/{postId}", produces = "application/json")
    Post getPostById(@PathVariable("postId") Long postId);
}

在这个例子中,我们配置了一个客户端用于访问 JSONPlaceholder APIs

@FeignClient 注解中的 value 是客户端名称(任意命名),url 指定了 API 的基础 URL。

由于这是一个 Feign 客户端,我们可以使用 Spring Web 注解来声明要调用的接口。

4. 配置

非常重要的一点是:每个 Feign 客户端都由一组可定制的组件构成。

Spring Cloud 会为每个命名的客户端按需创建默认的组件集合,使用 FeignClientsConfiguration 类进行配置,我们可以在下一节中看到如何自定义这些组件。

该类包含以下 Bean:

  • Decoder – ResponseEntityDecoder,封装了 SpringDecoder,用于解码响应
  • Encoder – SpringEncoder,用于编码请求体
  • Logger – Slf4jLogger 是 Feign 默认的日志记录器
  • Contract – SpringMvcContract,提供注解处理能力
  • Feign-Builder – HystrixFeign.Builder,用于构建组件
  • Client – LoadBalancerFeignClient 或默认的 Feign 客户端

4.1. 自定义 Bean 配置

如果我们想自定义其中某些 Bean,可以创建一个 Configuration 类,并将其添加到 @FeignClient 注解中:

@FeignClient(value = "jplaceholder",
  url = "https://jsonplaceholder.typicode.com/",
  configuration = ClientConfiguration.class)
public class ClientConfiguration {

    @Bean
    public OkHttpClient client() {
        return new OkHttpClient();
    }
}

在这个例子中,我们告诉 Feign 使用 OkHttpClient 替代默认客户端以支持 HTTP/2。

Feign 支持多种客户端,例如 ApacheHttpClient,它会在请求中发送更多头部信息,比如 Content-Length,某些服务器可能需要这些信息。

使用这些客户端时,别忘了在 pom.xml 中添加相应的依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

可以在 Maven Central 上找到 feign-okhttpfeign-httpclient 的最新版本。

4.2. 使用属性配置

除了使用 Configuration 类,我们也可以使用 application.properties 或 application.yaml 文件来配置 Feign 客户端,如下例所示:

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic

通过这个配置,我们将每个客户端的连接超时和读取超时设置为 5 秒,日志级别为 basic

我们也可以使用特定客户端名称进行配置:

feign:
  client:
    config:
      jplaceholder:

如果同时配置了 Configuration Bean 和属性文件,属性配置会覆盖 Bean 中的设置。

5. 拦截器

添加拦截器是 Feign 提供的另一个实用功能。

拦截器可以执行各种隐式任务,比如身份验证和日志记录,适用于每个 HTTP 请求/响应。

在本节中,我们将实现自己的拦截器,同时使用 Spring Cloud OpenFeign 提供的现成拦截器,两者都会 为每个请求添加基本的身份验证头

5.1. 实现 RequestInterceptor

让我们实现一个自定义请求拦截器:

@Bean
public RequestInterceptor requestInterceptor() {
  return requestTemplate -> {
      requestTemplate.header("user", username);
      requestTemplate.header("password", password);
      requestTemplate.header("Accept", ContentType.APPLICATION_JSON.getMimeType());
  };
}

要将拦截器添加到请求链中,只需将该 Bean 添加到 Configuration 类中,或像之前一样在属性文件中声明:

feign:
  client:
    config:
      default:
        requestInterceptors:
          com.baeldung.cloud.openfeign.JSONPlaceHolderInterceptor

5.2. 使用 BasicAuthRequestInterceptor

或者,我们可以使用 Spring Cloud OpenFeign 提供的 BasicAuthRequestInterceptor 类:

@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
    return new BasicAuthRequestInterceptor("username", "password");
}

简单粗暴,现在所有请求都会包含基本身份验证头。

6. Hystrix 支持

Feign 支持 Hystrix,如果我们启用了它,就可以实现降级逻辑(fallback pattern)。

在降级模式中,当远程服务调用失败时,服务消费者不会抛出异常,而是执行备用代码路径,尝试通过其他方式完成操作。

要启用 Hystrix,只需在配置文件中添加 feign.hystrix.enabled=true

然后我们可以实现降级方法,当服务失败时会被调用:

@Component
public class JSONPlaceHolderFallback implements JSONPlaceHolderClient {

    @Override
    public List<Post> getPosts() {
        return Collections.emptyList();
    }

    @Override
    public Post getPostById(Long postId) {
        return null;
    }
}

为了让 Feign 知道我们提供了降级类,还需要在 @FeignClient 注解中设置 fallback 类:

@FeignClient(value = "jplaceholder",
  url = "https://jsonplaceholder.typicode.com/",
  fallback = JSONPlaceHolderFallback.class)
public interface JSONPlaceHolderClient {
    // APIs
}

7. 日志

每个 Feign 客户端都会默认创建一个日志记录器。

要启用日志,可以在 application.properties 文件中使用客户端接口的包名进行声明:

logging.level.com.baeldung.cloud.openfeign.client: DEBUG

或者,如果只想为某个特定客户端启用日志,可以使用完整类名:

logging.level.com.baeldung.cloud.openfeign.client.JSONPlaceHolderClient: DEBUG

注意:Feign 的日志仅响应 DEBUG 级别。

我们可以为每个客户端配置不同的 Logger.Level,以控制日志输出内容:

public class ClientConfiguration {
    
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.BASIC;
    }
}

可选的日志级别有四种:

  • NONE – 不记录日志(默认)
  • BASIC – 只记录请求方法、URL 和响应状态
  • HEADERS – 记录基础信息以及请求和响应头
  • FULL – 记录请求和响应的 body、headers 和 metadata

8. 错误处理

Feign 默认的错误处理器 ErrorDecoder.default 会抛出 FeignException

这种行为并不总是最合适的,所以 我们可以使用 CustomErrorDecoder 来自定义异常处理

public class CustomErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {

        switch (response.status()){
            case 400:
                return new BadRequestException();
            case 404:
                return new NotFoundException();
            default:
                return new Exception("Generic error");
        }
    }
}

然后,像之前一样,通过在 Configuration 类中添加 Bean 来替换默认的 ErrorDecoder

public class ClientConfiguration {

    @Bean
    public ErrorDecoder errorDecoder() {
        return new CustomErrorDecoder();
    }
}

9. 总结

在本文中,我们讨论了 Spring Cloud OpenFeign 及其在简单示例应用中的实现。

我们还了解了如何配置客户端、为请求添加拦截器,以及使用 HystrixErrorDecoder 进行错误处理。

一如既往,本文中的所有代码示例都可以在 GitHub 上找到。


原始标题:Introduction to Spring Cloud OpenFeign | Baeldung