OpenFeign

OpenFeign概述

  1. 官网文档:https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign

  2. GitHub 仓库:https://github.com/spring-cloud/spring-cloud-openfeign

  3. Feign 是一个 声明式的 Web 服务客户端,它让编写服务调用变得更加简单与优雅。开发者只需定义一个接口并通过注解描述调用规则,Feign 会在底层自动生成 HTTP 请求代码,从而简化服务之间的通信过程。它支持可插拔的注解体系(包括 Feign 原生注解和 JAX-RS 注解),同时允许开发者根据需要自定义编码器(Encoder)和解码器(Decoder),以满足不同的序列化需求。

  4. Spring Cloud 在 Feign 的基础上进行了深度集成与扩展,形成了 Spring Cloud OpenFeign。主要增强点包括:

    • 兼容 Spring MVC 注解:支持使用 @RequestMapping@GetMapping@PostMapping 等注解定义接口方法,使开发体验与本地 Controller 保持一致。
    • 集成 Spring Web 的消息转换机制:通过 HttpMessageConverter 实现与 Spring MVC 一致的请求与响应数据转换。
    • 支持主流微服务组件:无缝整合了:
      • Eureka:实现服务注册与发现;
      • Spring Cloud CircuitBreaker:提供服务熔断与容错机制;
      • Spring Cloud LoadBalancer:提供客户端负载均衡支持。

    通过这些集成,OpenFeign 实现了一个具备自动发现、负载均衡、熔断与降级能力的高层次 HTTP 客户端,使服务间调用更稳定、更灵活。

  5. 如今,OpenFeign 已成为 Spring Cloud 体系下微服务之间调用的事实标准。

OpenFeign的作用

  1. 在使用 Spring Cloud LoadBalancer 搭配 RestTemplate 时,我们通常通过封装 HTTP 请求形成一套模板化的调用方式。然而,这种方式在实际开发中存在一定的局限性——当多个服务需要调用同一依赖服务时,往往需要在不同模块中重复编写相似的调用逻辑。

  2. 为了减少这种重复代码,开发者通常会针对每个依赖服务封装独立的客户端类,以统一管理服务调用逻辑。但这种做法仍然需要手动维护大量模板代码,随着服务数量增多,维护成本和出错风险也随之上升。

  3. OpenFeign 正是基于此问题的进一步抽象与封装。它让服务间调用真正实现了 “声明即调用”:只需定义一个接口并使用注解进行配置(例如在接口上添加 @FeignClient 注解),即可完成对远程服务的绑定。

    OpenFeign 会根据接口定义自动生成调用实现,实现对服务提供方接口的统一封装,使调用方无需关心底层 HTTP 细节,从而极大简化了客户端的开发工作。换句话说,服务提供者定义接口清单,消费者只需通过 OpenFeign 按接口调用即可。

  4. 除了简化调用逻辑,OpenFeign 还深度集成了Spring Cloud LoadBalancer,在调用远程服务时自动实现客户端负载均衡。同时,它还可以与Alibaba Sentinel集成,提供熔断、限流、降级等容错机制。

    相比直接使用 LoadBalancer + RestTemplate 的方式,OpenFeign 以声明式编程的形式更为优雅与高效,让分布式系统中的服务调用既清晰又稳定。

OpenFeign使用步骤

  1. 微服务API接口 + @FeignClient注解标签。
  2. 服务消费者80 → 调用含有@FeignClient注解的API服务接口 → 服务提供者(8001/8002)
  3. 订单模块要去调用支付模块,订单和支付两个微服务,需要通过API接口解耦。
img

创建消费者模块

为了不修改之前的80订单模块,这里新建一个cloud-consumer-feign-order80模块

新建模块
img
修改pom.xml

引入spring-cloud-starter-openfeign

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.sun.cloud</groupId>
        <artifactId>cloud2025</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
 
    <artifactId>cloud-consumer-feign-order80</artifactId>
 
    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--SpringCloud consul discovery-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包 -->
        <dependency>
            <groupId>com.sun.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--web + actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--hutool-all-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!--fastjson2-->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
</project>
编写application.yaml
server:
  port: 80
 
spring:
  application:
    name: cloud-consumer-openfeign-order
  ####Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #优先使用服务ip进行注册
        service-name: ${spring.application.name}
主启动类

主启动类上面配置@EnableFeignClients表示开启OpenFeign功能并激活。

package com.sun.cloud;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
 
@SpringBootApplication
@EnableDiscoveryClient // 该注解用于向使用consul为注册中心时注册服务
@EnableFeignClients// 启用feign客户端,定义服务+绑定接口,以声明式的方法优雅而简单的实现服务调用
public class MainOpenFeign80 {
    public static void main(String[] args) {
        SpringApplication.run(MainOpenFeign80.class, args);
    }
}

创建微服务API接口

  1. 由于可能有多个模块都会用到8001支付模块提供的服务,所以,这些API接口,最好写到一个公用的模块中。然后在其他模块将该公共模块引入为依赖即可。

  2. 此前,我们已经创建了一个公共模块cloud-api-commons,只需要修改该模块即可。

cloud-api-commons模块添加依赖
<!--openfeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
cloud-api-commons新建服务接口PayFeignApi

value表示绑定哪一个微服务

package com.sun.cloud.apis;
 
import org.springframework.cloud.openfeign.FeignClient;
 
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi {
}
PayFeignApi接口中,定义方法

参考8001支付模块的controller中的方法

package com.sun.cloud.apis;
 
import com.sun.cloud.entities.PayDTO;
import com.sun.cloud.resp.ResultData;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
 
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi {
    /**
     * 新增一条支付相关流水记录
     * @param payDTO 流水记录的数据传输对象
     * @return ResultData
     */
    @PostMapping("/pay/add")
    ResultData addPay(@RequestBody PayDTO payDTO);
 
    /**
     * 按照主键记录查询支付流水信息
     * @param id 支付流水ID
     * @return ResultData
     */
    @GetMapping("/pay/get/{id}")
    ResultData getPayInfo(@PathVariable("id") Integer id);
 
    /**
     * openfeign天然支持负载均衡演示
     * @return String
     */
    @GetMapping(value = "/consul/info")
    String getInfoByConsul();
}
注意

接口写好后,重新install,更新本地库的jar包

完善消费者模块

  1. 前面新建了消费者模块cloud-consumer-feign-order80,现在需要拷贝以前的订单80模块cloud-consumer-order80中的controller。
  2. 为了测试OpenFeign,就不复制RestTemplateConfig相关的文件。并且删除RestTemplateLoadBalancer相关的代码。
  3. 修改消费者模块cloud-consumer-feign-order80中的controller,调用cloud-api-commons模块中的Api接口方法。
image.png
package com.sun.cloud.controller;
 
import com.sun.cloud.apis.PayFeignApi;
import com.sun.cloud.entities.PayDTO;
import com.sun.cloud.resp.ResultData;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class OrderController {
 
    @Resource
    private PayFeignApi payFeignApi;
 
    @PostMapping("/feign/pay/add")
    public ResultData addOrder(PayDTO payDTO) {
        System.out.println("第一步:模拟本地addOrder新增订单成功(省略sql操作),第二步:再开启addPay支付微服务远程调用");
        return payFeignApi.addPay(payDTO);
    }
 
 
    @GetMapping("/feign/pay/get/{id}")
    public ResultData getPayInfo(@PathVariable("id") Integer id) {
        System.out.println("-------支付微服务远程调用,按照id查询订单支付流水信息");
        return payFeignApi.getPayInfo(id);
    }
 
    @GetMapping(value = "/feign/consul/info")
    public String getInfoByConsul() {
        return payFeignApi.getInfoByConsul();
    }
}

测试

测试接口调用
img
测试负载均衡
imgimgimg

OpenFeign高级特性

OpenFeign超时控制

在 Spring Cloud 微服务架构中,大部分服务间调用都是通过 OpenFeign 实现的。

在业务逻辑较为简单的场景下,默认的超时配置通常足够使用;但在一些计算复杂、依赖较多或调用链较长的业务中,服务端可能需要较长时间才能返回结果,这时就容易触发 Read Timeout 异常。为了避免这种情况,定制化配置超时时间就显得非常必要。

默认超时行为

默认情况下,OpenFeign 客户端的超时等待时间为 60 秒。如果服务端在该时间内未能完成处理并返回结果,Feign 客户端就会抛出超时异常,从而导致调用失败。

这种默认设置在某些业务中可能过长(影响系统响应速度),而在另一些复杂业务中又可能过短(导致请求过早中断),因此需要根据实际业务情况进行合理调整。

超时控制配置项

OpenFeign 提供了两类主要的超时控制参数,可以通过配置文件进行设置:

  • connectTimeout连接超时时间,指客户端与服务端建立连接所允许的最大等待时间。 如果在指定时间内无法建立连接,将会抛出连接超时异常。
  • readTimeout请求处理超时时间,指连接建立成功后等待服务端响应数据的最大时间。 超出此时间仍未收到响应,则会触发读取超时异常。
建议

合理设置这两个超时参数,可以有效避免由于网络波动、服务处理耗时过长或下游系统延迟导致的请求失败,从而提升系统的稳定性与可靠性。

全局控制

spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            #连接超时时间
            connectTimeout: 3000
            #读取超时时间
            readTimeout: 3000

指定配置

spring:
  cloud:
    openfeign:
      client:
        config:
          # 服务名称
          cloud-payment-service:
            #连接超时时间
            connectTimeout: 5000
            #读取超时时间
            readTimeout: 5000

OpenFeign重试机制

  1. 默认未开启重试机制
  2. 要开启重试机制,则新增配置类FeignConfig并修改Retryer配置
package com.sun.cloud.config;
 
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@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);
    }
}

设置不重试

package com.sun.cloud.config;
 
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class FeignConfig {
    @Bean
    public Retryer myRetryer() {
        return Retryer.NEVER_RETRY; //Feign默认配置是不走重试策略的
    }
}

OpenFeign默认HttpClient修改

  1. OpenFeign中http client,如果不做特殊配置,OpenFeign默认使用JDK自带的HttpURLConnection发送HTTP请求,HttpURLConnection没有连接池、性能和效率比较低。
  2. 修改cloud-consumer-openfeign-order,使用Apache HttpClient 5替换OpenFeign默认的HttpURLConnection

添加依赖

<!-- httpclient5-->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
</dependency>
<!-- feign-hc5-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-hc5</artifactId>
</dependency>

yaml文件开启配置

# Apache HttpClient5 配置开启
spring:
  cloud:
    openfeign:
      httpclient:
        hc5:
          enabled: true

OpenFeign请求/响应压缩

对请求和响应进行GZIP压缩

Spring Cloud OpenFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的两个参数设置,就能开启请求与相应的压缩功能:

spring:
  cloud:
    openfeign:
      compression:
        request:
          enabled: true
        response:
          enabled: true

细粒度化设置

对请求压缩做一些更细致的设置,比如下面的配置内容指定压缩的请求数据类型并设置了请求压缩的大小下限,只有超过这个大小的请求才会进行压缩:

spring:
  cloud:
    openfeign:
      compression:
        request:
          enabled: true
          #触发压缩数据类型
          mime-types: text/xml,application/xml,application/json
          #最小触发压缩的大小
          min-request-size: 2048

完整配置

spring:
  cloud:
    openfeign:
      compression:
        request:
          enabled: true
          min-request-size: 2048 #最小触发压缩的大小
          mime-types: text/xml,application/xml,application/json #触发压缩数据类型
        response:
          enabled: true

OpenFeign日志打印功能

  1. Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节,说白了就是对Feign接口的调用情况进行监控和输出。

  2. 日志级别

    • NONE:默认的,不显示任何日志;

    • BASIC:仅记录请求方法、URL、响应状态码及执行时间;

    • HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;

    • FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

配置日志bean

package com.sun.cloud.config;
 
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;
    }
}

yaml文件开启配置

logging.level + 含有@FeignClient注解的完整带包名的接口名 + debug

# feign日志以什么级别监控哪个接口
logging:
  level:
    com:
      sun:
        cloud:
          apis:
            PayFeignApi: debug 

相关文章

评论区