使用OpenFeign的五个步骤和七个高级功能

开发 前端
Feign是一个开源的Java库,用于简化Web请求的过程。Feign是一个流行的Java HTTP客户端库,具有多种优势和特性,是开发基于HTTP的微服务和应用程序的不错选择。

什么是Feign?

Feign是一个开源的Java库,用于简化Web请求的过程。

它通过提供更高级别的抽象来简化RESTful Web服务的实现,消除了样板代码的需要,使代码库更具可读性和可维护性。

Feign是一个流行的Java HTTP客户端库,具有多种优势和特性,是开发基于HTTP的微服务和应用程序的不错选择。

什么是声明式HTTP客户端?

声明式HTTP客户端是一种通过编写Java接口来发起HTTP请求的方式。

Feign会根据我们提供的注解,在接口背后生成实际的实现。

为何使用Feign?

如果我们需要调用大量的API,手动编写HTTP代码或使用难以维护的代码生成方式并不理想。

使用Feign,我们可以通过一个简单的小接口来描述API,让Feign在运行时解释和实现该接口,这样更加简便且易于维护。

谁适合使用Feign?

如果我们在Java代码中需要发起HTTP请求,并且不想编写样板代码,或者不想直接使用像Apache httpclient这样的库,那么Feign是一个很好的选择。

创建基本的Feign客户端

步骤0:添加bom

我们依然使用feign-bom作为版本管理:

<project>
    ……
    <properties>
        <openfeign.version>13.4</openfeign.version>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.github.openfeign</groupId>
                <artifactId>feign-bom</artifactId>
                <version>${openfeign.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

步骤1:添加Feign依赖

引入feign-core:

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

步骤2:定义客户端接口

该接口通常包含带有Feign注解的方法声明。

我们将为服务器上要调用的每个REST端点声明一个客户端接口方法,这些只是声明,无需实现这些方法,Feign会为我们完成。方法签名应包括HTTP方法以及所有必需的数据。

假设我们要定义一个表示计算器服务的接口,它有执行加法、减法、乘法和除法等计算的简单API方法:

public interface CalculatorService {
    /**
     * 两整数相加。
     *
     * @param firstNumber  第一个整数
     * @param secondNumber 第二个整数
     * @return 两数之和
     */
    @RequestLine("POST /operations/add?firstNumber={firstNumber}&secondNumber={secondNumber}")
    Long add(@Param("firstNumber") Long firstNumber,
             @Param("secondNumber") Long secondNumber);

    /**
     * 两整数相减。
     *
     * @param firstNumber  第一个整数
     * @param secondNumber 第二个整数
     * @return 两数之差
     */
    @RequestLine("POST /operations/subtract?firstNumber={firstNumber}&secondNumber={secondNumber}")
    Long subtract(@Param("firstNumber") Long firstNumber,
                  @Param("secondNumber") Long secondNumber);

    /**
     * 两整数相乘。
     *
     * @param firstNumber  第一个整数
     * @param secondNumber 第二个整数
     * @return 两数之积
     */
    @RequestLine("POST /operations/multiply?firstNumber={firstNumber}&secondNumber={secondNumber}")
    Long multiply(@Param("firstNumber") Long firstNumber,
                  @Param("secondNumber") Long secondNumber);

    /**
     * 两整数相除。
     *
     * @param firstNumber  第一个整数
     * @param secondNumber 第二个整数,不应为零
     * @return 两数之商
     */
    @RequestLine("POST /operations/divide?firstNumber={firstNumber}&secondNumber={secondNumber}")
    Long divide(@Param("firstNumber") Long firstNumber,
                @Param("secondNumber") Long secondNumber);
}

@RequestLine定义了请求的HTTP方法和url模板,而@Param定义了一个模板变量。

不用担心,稍后我们将详细了解OpenFeign提供的注解。

步骤3:创建客户端对象

我们使用Feign的builder()方法来准备客户端:

final CalculatorService target = Feign
       .builder()
       .decoder(new JacksonDecoder())
       .target(CalculatorService.class, HOST);

准备客户端的方式有很多种,具体取决于我们的需求。

上面给出的代码片段只是准备客户端的一种简单方式。

我们注册了用于解码JSON响应的解码器,解码器可以根据服务返回的响应的内容类型进行更改,稍后我们将详细了解解码器。

步骤4:使用客户端进行API调用

现在让我们调用客户端的add()方法:

final Long result = target.add(firstNumber, secondNumber);

与其他HTTP客户端相比,使用Feign HTTP客户端调用服务相当简单。

Feign注解

OpenFeign使用一组注解来定义HTTP请求及其参数,常用的OpenFeign注解及其示例如下:

注解

描述

示例

@RequestLine

指定HTTP方法和路径

@RequestLine("GET /resource/{id}")

@Headers

指定请求的HTTP头

@Headers("Authorization: Bearer {token}")

@QueryMap

将查询参数映射到请求

@QueryMap Map<String, Object> queryParams

@Body

发送特定对象作为请求体

@Body RequestObject requestObject

@Param

向请求添加查询参数

@Param("id") long resourceId

@Path

替换路径中的模板变量

@Path("id") long resourceId

@RequestHeader

向请求添加头

@RequestHeader("Authorization") String authToken

@Headers

指定请求的其他头

@Headers("Accept: application/json")

这些注解可以用来定义OpenFeign客户端接口,使其易于使用OpenFeign与远程服务进行交互。我们可以根据特定的API要求混合和匹配这些注解。

处理响应

Feign还提供了一种声明式的API集成方法。

与手动编写处理响应或错误的样板代码不同,Feign允许我们定义自定义处理程序并将其注册到Feign构建器中。

这不仅减少了我们需要编写的代码量,还提高了可读性和可维护性。

让我们看一个解码器的示例:

final CalculatorService target = Feign.builder()
       .encoder(new JacksonEncoder())
       .decoder(new JacksonDecoder())
       .target(CalculatorService.class, HOST);

这段给定的代码片段展示了创建一个Feign客户端,使用Jackson进行请求编码和响应解码:

  • encoder(new JacksonEncoder()):在这里,为Feign客户端设置了一个JacksonEncoder。JacksonEncoder是Feign Jackson模块的一部分,用于将Java对象编码为JSON格式的HTTP请求体。当需要在请求体中发送对象时,这特别有用。
  • decoder(new JacksonDecoder()):同样,为Feign客户端设置了一个JacksonDecoder。JacksonDecoder负责将服务器的JSON响应解码为Java对象,它将JSON响应反序列化为相应的Java对象。

处理错误

错误处理是构建健壮可靠应用程序的关键方面,尤其是在进行远程API调用时。Feign提供了强大的功能来帮助有效地处理错误。

Feign让我们对处理意外响应有更多的控制。我们可以通过构建器注册一个自定义的ErrorDecoder。

final CalculatorService target = Feign.builder()
       .errorDecoder(new CalculatorErrorDecoder())
       .target(CalculatorService.class, HOST);

以下是一个错误处理的示例:

public class CalculatorErrorDecoder implements ErrorDecoder {
    private final ErrorDecoder defaultErrorDecoder = new Default();

    @Override
    public Exception decode(String methodKey, Response response) {
        ExceptionMessage message = null;
        try (InputStream bodyIs = response.body().asInputStream()) {
            ObjectMapper mapper = new ObjectMapper();
            message = mapper.readValue(bodyIs, ExceptionMessage.class);
        } catch (IOException e) {
            return new Exception(e.getMessage());
        }
        final String messageStr = message == null? "" : message.getMessage();
        switch (response.status()) {
            case 400:
                return new RuntimeException(messageStr.isEmpty()
                       ? "Bad Request"
                        : messageStr
                );
            case 401:
                return new RetryableException(response.status(),
                        response.reason(),
                        response.request().httpMethod(),
                        null,
                        response.request());
            case 404:
                return new RuntimeException(messageStr.isEmpty()
                       ? "Not found"
                        : messageStr
                );
            default:
                return defaultErrorDecoder.decode(methodKey, response);
        }
    }
}

所有HTTP状态不在HTTP 2xx范围内的响应,例如HTTP 400,都将触发ErrorDecoder的decode()方法。

在这个重写的decode()方法中,我们可以处理响应,将失败包装成自定义异常或执行任何其他处理。

我们甚至可以通过抛出RetryableException来再次重试请求,这将调用注册的Retryer。Retryer将在高级技术部分详细解释。

高级技术

集成编码器/解码器

编码器和解码器分别用于对请求和响应数据进行编码/解码。我们根据请求和响应的内容类型选择它们,例如,对于JSON数据可以使用Gson或Jackson。

以下是一个使用Jackson编码器和解码器的示例:

final CalculatorService target = Feign.builder()
       .encoder(new JacksonEncoder())
       .decoder(new JacksonDecoder())
       .target(CalculatorService.class, HOST);

更改HTTP客户端

默认情况下,Feign使用其自带的HTTP客户端。将Feign的默认HTTP客户端从原始的Apache HTTP Client更改为其他库(如OkHttp)的主要动机是为了获得更好的性能、改进的功能以及与现代HTTP标准更好的兼容性。

现在让我们看看如何覆盖HTTP客户端:

final CalculatorService target = Feign.builder()
       .client(new OkHttpClient())
       .target(CalculatorService.class, HOST);

配置日志记录器

SLF4JModule用于将Feign的日志记录发送到SLF4J。通过SLF4J,我们可以轻松使用我们选择的日志后端(Logback、Log4J等)。

以下是一个构建客户端的示例:

final CalculatorService target = Feign.builder()
       .logger(new Slf4jLogger())
       .logLevel(Level.FULL)
       .target(CalculatorService.class, HOST);

要在Feign中使用SLF4J,需要将SLF4J模块和我们选择的SLF4J绑定添加到类路径中,然后按照上述方式配置Feign使用Slf4jLogger。

配置请求拦截器

Feign中的请求拦截器允许我们在将HTTP请求发送到远程服务器之前对其进行自定义和操作。它们可用于多种目的,例如添加自定义头、日志记录、身份验证或请求修改。

以下是我们可能希望在Feign中使用请求拦截器的原因:

  • 身份验证:我们可以使用请求拦截器向每个请求添加身份验证令牌或凭据,例如添加带有JWT令牌的“Authorization”头。
  • 日志记录:拦截器有助于记录传入和传出的请求和响应,这对于调试和监控很有用。
  • 请求修改:我们可以在发送请求之前修改请求,包括更改头、查询参数甚至请求体。
  • 速率限制:通过检查发出的请求数量来决定是否允许或阻止请求,从而实现速率限制。
  • 缓存:根据特定标准缓存请求/响应数据。

以下是一个演示如何使用请求拦截的代码片段:

static class AuthorizationInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        // 检查令牌是否存在,如果不存在则添加它
        template.header("Authorization", "Bearer " + generatedToken);
    }
}

public class CalculatorServiceTest {
    public static void main(String[] args) {
        final AuthorizationInterceptor interceptor = new AuthorizationInterceptor();
        final CalculatorService target = Feign.builder()
               .requestInterceptor(interceptor)
               .target(CalculatorService.class, HOST);
    }
}

实现RequestInterceptor并覆盖其apply()方法,以根据需要对请求进行任何修改。

配置重试器

OpenFeign Retryer是一个组件,允许我们配置Feign在请求失败时如何处理重试。它对于处理网络通信中的瞬时故障特别有用,我们可以指定Feign应自动重试失败请求的条件。

重试器配置

要在OpenFeign中使用重试器,需要提供Retryer接口的实现。Retryer接口有两个方法:

  • boolean continueOrPropagate(int attemptedRetries, int responseStatus, Request request):此方法用于确定是继续重试还是传播错误。它接受尝试重试的次数、HTTP响应状态和请求作为参数,并返回true以继续重试或false以传播错误。
  • Retryer clone():此方法创建重试器实例的克隆。

默认重试器

Feign提供了一个默认的重试器实现,名为Retryer.Default。当我们创建Feign客户端而未显式指定自定义重试器时,将使用此默认重试器。

它提供了两个工厂方法来创建Retryer对象。

第一个工厂方法不需要任何参数:

public Default() {
    this(100L, TimeUnit.SECONDS.toMillis(1L), 5);
}

它定义了一个简单的重试策略,具有以下特点:

  • 最大尝试次数:对于失败的请求,最多允许5次重试尝试。
  • 退避期:在重试之间使用指数退避策略,从100毫秒的退避开始,每次后续重试将退避时间加倍。
  • 可重试异常:如果请求导致任何被认为可重试的异常(通常包括网络相关异常,如连接超时或套接字异常),则会重试请求。

第二个工厂方法需要一些参数。如果默认配置不适合我们,可以使用它:

public Default(long period, long maxPeriod, int maxAttempts)

// 使用它创建重试器
new Retryer.Default(1, 100, 10);

虽然Feign提供的默认重试器涵盖了许多常见的重试场景,但在某些情况下,我们可能希望定义自定义重试器。以下是定义自定义重试器的一些动机:

  • 精细控制:如果我们需要对默认重试行为进行更多控制,例如指定不同的最大重试次数或自定义退避策略,自定义重试器允许我们根据特定要求定制行为。
  • 重试逻辑:在某些情况下,我们可能只希望针对特定的响应代码或异常重试请求。自定义重试器允许我们实现自己的逻辑来确定何时应该进行重试。
  • 日志记录和指标:如果我们想要记录或收集与重试尝试相关的指标,实现自定义重试器提供了添加此功能的机会。
  • 与断路器集成:如果我们将断路器模式与Feign结合使用,自定义重试器可以与断路器的状态集成,以便在决定何时重试或何时打开断路器时做出更明智的决策。
  • 非标准重试策略:对于不适合默认重试器提供的标准重试策略的场景,例如速率限制的API或具有特定重试要求的API,我们可以定义适合我们用例的自定义重试器。

以下是在OpenFeign中实现自定义Retryer的示例:

public class CalculatorRetryer implements Retryer {
    /**
     * 重试之间等待的毫秒数
     */
    private final long period;

    /**
     * 最大重试次数
     */
    private final int maxAttempts;

    private int attempt = 1;

    @Override
    public void continueOrPropagate(RetryableException e) {
        log.info("Feign retry attempt {} of {} due to {}",
                attempt,
                maxAttempts,
                e.getMessage());
        if (++attempt > maxAttempts) {
            throw e;
        }
        if (e.status() == 401) {
            try {
                Thread.sleep(period);
            } catch (InterruptedException ex) {
                throw e;
            }
        } else {
            throw e;
        }
    }

    @Override
    public Retryer clone() {
        return this;
    }

    public int getRetryAttempts() {
        return attempt - 1; // 减去1以排除初始尝试
    }
}

它专门重试HTTP 401错误。

总之,当我们需要对HTTP请求中的重试处理方式有更大的控制权和灵活性时,就有必要在Feign中创建自定义重试器。当我们的需求与默认重试器的行为不同时,自定义重试器允许我们根据特定用例修改重试逻辑。

断路器

断路器通常使用单独的库或工具(如Netflix Hystrix、Resilience4j或Spring Cloud Circuit Breaker)来实现。

为何使用断路器?

在Feign中使用断路器的主要动机是增强基于微服务的应用程序的弹性。以下是一些关键原因:

  • 故障隔离:断路器通过隔离故障组件,防止一个服务中的故障级联到其他服务。
  • 快速失败:当电路打开(表示故障状态)时,后续请求会“快速失败”,而不会尝试向可能无响应或故障的服务发出调用,从而减少延迟和资源消耗。
  • 优雅降级:当依赖服务出现问题时,断路器允许应用程序优雅降级,确保它能够继续提供一组简化的功能。
  • 监控和指标:断路器提供指标和监控功能,使我们能够跟踪服务的健康状况和性能。

配置断路器

HystrixFeign用于配置Hystrix提供的断路器支持。

Hystrix是一个延迟和容错库,旨在隔离分布式环境中对远程系统、服务和第三方库的访问点。它有助于阻止级联故障,并在故障不可避免的复杂分布式系统中实现弹性。

要在Feign中使用Hystrix,需要将Hystrix模块添加到类路径中,并使用HystrixFeign构建器,如下所示:

final CalculatorService target = HystrixFeign.builder()
      .target(CalculatorService.class, HOST);

让我们看看如何使用回退类来处理服务错误。

在Hystrix中,回退类是为Hystrix命令定义回退逻辑的另一种方式,而不是直接在Hystrix命令类的getFallback方法中定义回退逻辑。回退类提供了关注点分离,使我们能够将命令类专注于主要逻辑,并将回退逻辑委托给单独的类。这可以提高代码的组织性和可维护性。

以下是为CalculatorService实现回退的示例代码:

@Slf4j
public class CalculatorHystrixFallback implements CalculatorService {

    @Override
    public Long add(Long firstNumber, Long secondNumber) {
        log.info("[Fallback add] Adding {} and {}", firstNumber, secondNumber);
        return firstNumber + secondNumber;
    }

    @Override
    public Long subtract(Long firstNumber, Long secondNumber) {
        return null;
    }

    @Override
    public Long multiply(Long firstNumber, Long secondNumber) {
        return null;
    }

    @Override
    public Long divide(Long firstNumber, Long secondNumber) {
        return null;
    }
}

为了演示回退,我们仅实现了add方法。然后在构建客户端时使用此回退:

final CalculatorHystrixFallback fallback = new CalculatorHystrixFallback();
final CalculatorService target = HystrixFeign.builder()
      .decoder(new JacksonDecoder())
      .target(CalculatorService.class,
               HOST, fallback);

当add端点返回错误或电路打开时,Hystrix将调用add回退方法。

收集指标

Feign本身不像其他一些库或框架那样提供内置的指标功能API。

与Feign相关的指标,如请求持续时间、错误率或重试次数,通常需要使用外部库或工具来收集和跟踪。

在Java应用程序中收集指标的流行库包括Micrometer和Dropwizard Metrics。

以下是如何使用常用的Micrometer库来收集和报告与Feign调用相关的指标:

public class CalculatorServiceTest {
    public static void main(String[] args) {
        final CalculatorService target = Feign.builder()
              .addCapability(new MicrometerCapability())
              .target(CalculatorService.class, HOST);
        target.contributors("OpenFeign", "feign");
        // 从此时起指标将可用
    }
}

请注意,我们需要将Micrometer作为依赖项添加到项目中并进行适当配置。

文末总结

本文算是温故知新的一篇,由浅入深介绍了使用OpenFeign的5个步骤和7个高级功能,OpenFeign的使用方面,已经涵盖全了。后续再看看在SpringCloud中,OpenFeign的使用,以及在于Ribbon等组件结合使用时,又能碰撞出哪些火花。

责任编辑:武晓燕 来源: 看山的小屋
相关推荐

2010-04-09 09:55:43

Oracle sqlp

2022-08-02 20:22:01

SaaS安全网络攻击

2014-03-12 15:23:20

2011-03-02 09:34:58

AppFuse

2023-04-25 12:45:09

2024-05-07 08:00:00

自然语言处理机器学习

2023-07-10 13:28:43

智能建筑工具

2023-03-06 08:48:52

2022-02-15 11:03:40

SD-WAN软件定义WAN

2015-12-23 09:48:32

2023-06-01 13:09:09

智能建筑数字孪生

2009-12-25 14:52:49

2019-07-11 15:26:50

数据中心技术数据网络

2022-04-13 10:25:08

基础设施IT 团队

2021-11-22 12:13:54

Linuxwget 命令

2022-07-28 11:33:23

数据分析经验

2022-10-20 20:47:58

2022-03-01 20:20:18

云迁移云计算

2020-10-10 16:07:16

物联网设备物联网IOT

2024-07-26 10:28:50

前端开发CSS
点赞
收藏

51CTO技术栈公众号