Fegin 前言以及介绍

Fegin是做什么

首先,Fegin 是一个声明式的 HTTP 客户端,它的主要作用是简化微服务之间的 HTTP 调用流程。

通过 Feign,开发者可以像调用本地方法一样调用远程服务的接口,无需手动编写复杂的 HTTP 请求代码(如使用 RestTemplate 构建请求、处理响应等)。

Feign 不做任何请求处理,通过处理注解相关信息生成 Request,并对调用返回的数据进行解码,从而实现 简化 HTTP API 的开发,和 RestTemple 一样,Feign 基于接口和注解的方式,自动生成 HTTP 请求的实现,开发者只需定义接口并添加注解(如 @FeignClient),即可实现对远程服务的调用,大幅减少模板代码。

Spring Cloud 对Feign进行了增强,使得Feign支持了更多实用的注解,并整合了 Ribbon 和 Eureka,从而让 Feign 的使用更加方便。与 Spring Cloud 的集成更加一体化。

在Spring Cloud生态中,Feign 作为服务消费方调用服务提供方的桥梁,是 Spring Cloud 中 “服务消费层” 的关键实现,使得微服务间的协作更加简洁、高效。

为什么要使用 Fegin

首先,HTTP 客户端的核心作用就是,封装底层 HTTP 协议的细节(如建立连接、构造请求、解析响应等),让开发者能以更简洁的方式实现服务间调用。

其实 RestTemple 和 Fegin 在 Spring 生态中的核心定位是不一样的

  • RestTemplate命令式的客户端,是 Spring 框架提供的基础 HTTP 客户端,使用时需要手动编写请求逻辑;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 注入 RestTemplate
    @Autowired
    private RestTemplate restTemplate;

    // 调用远程服务
    public User getUserById(Long id) {
    // 手动拼接 URL(可能需要硬编码服务地址)
    String url = "http://user-service/users/" + id;
    // 手动指定请求方法(GET)和返回类型
    return restTemplate.getForObject(url, User.class);
    }
    1. 代码冗余:每次调用都需要编写 URL 拼接、请求方法指定等重复代码;
    2. 服务地址硬编码:如果服务地址变化(如使用服务注册中心动态获取地址),需要额外处理;
    3. 集成性差:与 Spring Cloud 生态组件(如负载均衡 Ribbon、熔断 Hystrix)的集成需要手动配置;
    4. 可读性低:大量模板代码掩盖了业务逻辑,不利于维护。
  • Feign声明式的客户端,通过接口和注解自动生成请求实现。是 Spring Cloud 官方推荐的服务调用工具,它基于 “声明式” 思想,解决了 RestTemplate 的上述问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 声明 Feign 客户端(指定要调用的服务名)
    @FeignClient(name = "user-service") // 服务名从注册中心获取
    public interface UserServiceClient {
    // 声明接口方法(与远程服务的接口保持一致)
    @GetMapping("/users/{id}")
    User getUserById(@PathVariable("id") Long id);
    }

    // 使用时直接注入接口,像调用本地方法一样使用
    @Autowired
    private UserServiceClient userClient;

    public User getUser(Long id) {
    return userClient.getUserById(id); // 无需关心 HTTP 细节
    }
    • Feign 只需定义接口并添加注解,无需编写实现逻辑,底层会自动生成 HTTP 请求代码
    • Feign 天生为 Spring Cloud 设计,无需额外配置即可集成核心组件
    • Feign 接口的方法定义与远程服务的 Controller 接口完全一致

我们再看一下我们之前利用 RestTemple 发起远程调用的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;

// 查询商品库存
public Integer getProductStock(Long productId, Integer shopId) {
// 1. 硬编码或复杂的 URL 拼接(包含多个参数时更混乱)
String url = "http://product-service/api/v1/stock?productId=" + productId + "&shopId=" + shopId;

// 2. 手动指定请求方法和响应处理
ResponseEntity<Integer> response = restTemplate.getForEntity(url, Integer.class);

// 3. 手动处理响应状态(可能被忽略,导致异常)
if (response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
} else {
throw new RuntimeException("查询库存失败");
}
}
}

这样存在下面的问题:

  • 代码可读性太差,而且容易造成多人多思路的差异,业务逻辑被掩盖
  • 参数复杂,url 维护不方便
  • 与微服务生态集成繁琐,若要添加负载均衡、熔断等能力,需要额外编写代码:

同样的场景,用 Feign 实现,看看 Fegin 是如何使用声明式来优雅解决上述问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 定义 Feign 接口(专注于接口声明,与远程服务保持一致)
@FeignClient(name = "product-service") // 服务名从注册中心获取
public interface ProductServiceClient {
// 直接复用远程接口的路径、参数和返回值定义
@GetMapping("/api/v1/stock")
Integer getProductStock(
@RequestParam("productId") Long productId,
@RequestParam("shopId") Integer shopId
);
}

// 2. 业务代码中直接调用(像用本地方法一样)
@Service
public class OrderService {
@Autowired
private ProductServiceClient productClient;

public Integer getProductStock(Long productId, Integer shopId) {
// 无需关心 URL、请求方法、响应处理,直接调用接口
return productClient.getProductStock(productId, shopId);
}
}
  • 声明式编程,聚焦业务
  • 参数与接口强绑定,易维护
  • 零配置集成微服务能力

所以说,RestTemplate 作为通用 HTTP 客户端,更适合简单的、非微服务场景的 HTTP 调用。而在 Spring Cloud 微服务架构中,服务间调用频繁、接口多变、需要集成多种中间件,Feign 通过声明式设计、强类型接口、生态无缝集成,完美解决了 RestTemplate 带来的各种问题

Fegin 的原理解析

Fegin 的整体运行流程

首先,通过应用启动类上的 `@EnableFeignClients 注解开启 Feign 的装配和远程代理实例创建。

@EnableFeignClients注解源码中可以看到导入了FeignClientsRegistrar类,该类用于扫描@FeignClient注解过的RPC接口

接着,通过对@FeignClient注解RPC接口进行扫描并且注册为 Bean

  • 扫描 @FeignClient 接口,遍历类路径,识别所有被 @FeignClient 标注的接口(
  • 注册 FeignClientFactoryBean,为每个接口创建 FeignClientFactoryBean 并注册到 Spring IOC 容器。后续当其他组件依赖该接口时,Spring 会通过 FactoryBean.getObject() 生成接口的代理实例。

生成动态代理由FeignClientFactoryBeanReflectiveFeign创建远程调用的动态代理实例。

如果应用某些地方需要注入RPC接口的实例(比如被@Resource引用),Spring就会通过注册的FactoryBean工厂类实例的getObject()方法获取RPC接口的动态代理实例。

通过代理对象调用接口方法时,Feign 会拦截调用,完成请求构建、负载均衡、HTTP 发送等流程。

在创建RPC接口的动态代理实例时,Feign会为每一个RPC接口创建一个调用处理器 InvocationHandler,也会为接口的每一个RPC方法创建一个方法处理器MethodHandler,并且将方法处理器缓存在调用处理器InvocationHandlerdispatch映射成员中。

在创建动态代理实例时,Feign也会通过RPC方法的注解为每一个RPC方法生成一个RequesTemplate请求模板实例,RequestTemplate中包含请求的所有信息,如请求URL、请求类型(如GET)、请求参数等。

image-20250725101537605

之后发生RPC调用时,通过动态代理实例类完成远程的HTTP调用。

SynchronousMethodHandler 是处理请求的核心,负责将方法调用转换为 HTTP 请求,通过 buildTemplateFromArgs.create(args) 结合 MethodMetadata(静态注解信息)和方法参数(动态值),生成包含完整请求信息的 RequestTemplate

之后MethodHandler会结合实际的调用参数,通过RequesTemplate模板实例生成Request请求实例。最后,将Request请求实例交给feign.Client客户端实例进一步完成HTTP请求处理。

在完成远程HTTP调用前需要进行客户端负载均衡的处理。

Fegin 如何实现远程调用

这个图展示了 Feign 框架实现服务调用的原理,它根据被 Fegin 注解标记的接口,使用动态代理生成动态代理类,我们使用 Fegin 生成的代理类构建并且发送了 HTTP 请求并得到响应结果。

image-20250723105411825

详细一些的 Feign 远程调用的工作流程的完整流程可以参考下图

image-20250723161407855

Feign远程调用的工作完整流程

Fegin 的请求如何构建

Feign 根据我们编写的 interface 动态生成一个实现了这个 interface 的代理实例,我们的代码就是通过这个代理实例实现了真正的远程调用 API 的逻辑。我们来分析看看 Feign 是如何生成这个代理实例的并且实现服务间的调用的。

这种设计离不开 Feign 的核心思想,通过接口声明定义远程调用的契约,再通过动态代理将接口方法转换为实际的 HTTP 请求。开发者只需定义接口并添加注解(如 @FeignClient),Feign 会自动处理参数封装、URL 映射、请求发送和响应解析等工作。

首先,Spring 启动时,Feign 会扫描带有 @FeignClient 注解的接口。

对于每个被扫描到的接口,Feign 会通过 Feign.Builder 创建一个动态代理对象(JDK 动态代理,基于接口)。这个代理对象会被注入到 Spring 容器中,替代接口成为实际的调用者。

1
2
3
4
5
6
// 开发者定义的Feign接口
@FeignClient(name = "user-service")
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}

这时候,上面我们定义的 Feign 接口(带 @FeignClient 注解)本质上是 “空接口”,里面的方法(如 getUserById)没有具体实现。但是,空接口不代表不能用,像这样的接口会在扫描到后被 Fegin 动态代理,当调用 getUserById 方法时,实际执行的是代理对象的逻辑。

当我们在代码中调用 userClient.getUserById(1L) 时,Feign 就会把这个接口方法调用转换成实际的 HTTP 请求(比如 GET http://user-service/users/1)。

HTTP请求的构建逻辑如下, Feign 启动时会扫描 Feign 接口中的所有方法,对每个方法做两件事:

  • 解析注解

    读取方法上的 Spring MVC 注解(@GetMapping@PostMapping等)和参数注解(@PathVariable@RequestParam等),提取关键信息:

    • HTTP 方法(GET/POST)
    • 请求路径(如 /users/{id}
    • 参数位置(路径参数、请求参数、请求体等)
    • 返回值类型(如 User
  • 创建 MethodHandler

    为每个方法创建一个 MethodHandler 实例,作为该方法的专属处理器。这个处理器就像一个 “管家”,负责管理该方法的所有远程调用细节。

所以说,MethodHandler接口方法的 “执行者”,每个接口方法对应一个 MethodHandler,它知道这个方法应该发什么样的 HTTP 请求。

而 Feign 中 HTTP 请求模板会封装一个 RequestTemplate 实例,它由 MethodHandler 持有,这样,Feign 基于解析到的元数据,使用 RequestTemplate 就会构建 HTTP 请求模板

  • 固化请求结构:把注解解析出的信息(HTTP 方法、路径、默认头信息等)固化成一个 “模板”。例如对于 @GetMapping("/users/{id}")RequestTemplate 会记录:
    • 方法:GET
    • 路径:/users/{id}{id} 是占位符)
    • 可能的默认头:Content-Type: application/json
  • 动态填充参数:当实际调用方法时(如传入 id=1),MethodHandler 会用实际参数替换 RequestTemplate 中的占位符(把 {id} 换成 1),生成最终可执行的 HTTP 请求。

所以说,RequestTemplate 是HTTP 请求的模板,MethodHandler 持有这个模板,Fegin在扫描到接口的时候,就会为其构建MethodHandler ,发送请求的时候调用时,MethodHandler 只需根据RequestTemplate进行参数的替换,就能生成完整请求。

Fegin 构建好的请求如何发送

当 Feign 完成 HTTP 请求的构建后,下一步就是将这个请求发送到目标服务。这一过程涉及负载均衡、HTTP 客户端选择、请求执行与重试等核心逻辑内容,所以 Fegin 有一套完整的协作机制来确保其中请求高效、可靠地送达。

请求的发送需要被触发,需要依靠InvocationHandler 拦截方法调用,这里就涉及到 Feign 动态代理的生成逻辑

当开发者调用 Feign 接口的方法(如 userClient.getUserById(1L))时,动态代理对象会通过 InvocationHandler(JDK 动态代理的核心拦截器,如果不懂,去看我动态代理那一章)拦截这个调用。此时,代理对象不会直接执行任何业务逻辑,而是根据反射机制,将调用转发给 Feign 内部的 MethodHandler,即该方法对应的专属处理器。

具体来说,InvocationHandlerinvoke 方法会做两件关键事情:

  1. 确定当前调用的方法对应的 MethodHandler(通过方法签名匹配)。
  2. 调用 MethodHandler.invoke(Object[] args) 方法,将方法参数(如 1L)传递给处理器,正式启动请求发送流程。
  3. 把参数发送到这里就是联系到上面的请求构建的部分了,MethodHandler会结合实际的调用参数,通过RequesTemplate模板实例生成Request请求实例。

之后,调用MethodHandler.invoke() 方法也不是意味着请求就发送过去了,而是把请求发送的任务交给了由MethodHandler方法处理器,开始进行HTTP请求处理。

image-20250723163457336

实际上 Feign 本身不直接实现 HTTP 通信,而是通过集成第三方 HTTP 客户端完成请求发送。默认情况下,Feign 使用 JDK 自带的 HttpURLConnection,但开发者可以通过配置切换为性能更优的客户端(如 OkHttp、HttpClient)。

Feign 通常与服务注册中心(如 Eureka、Nacos)配合使用,开发者在 @FeignClient 中指定的 name(如 user-service)是服务名,而非具体的 IP: 端口。因此,请求发送前需要通过负载均衡器将服务名转换为实际的服务地址。

这一过程由 LoadBalancerFeignClient(Feign 整合 Spring Cloud LoadBalancer 或 Ribbon 的核心类)完成,具体逻辑参考我之前的文章,我只进行简单的说明,如下:

  1. 获取服务实例列表:负载均衡器会向注册中心查询 user-service 对应的所有可用服务实例(如 192.168.1.100:8080192.168.1.101:8080)。
  2. 选择服务实例:根据负载均衡策略(如轮询、随机、权重等)从实例列表中挑选一个最优实例。例如,若采用轮询策略,可能选中 192.168.1.100:8080
  3. 拼接完整 URL**:将服务实例的地址与之前解析的请求路径组合,生成最终的请求 URL。例如,http://192.168.1.100:8080/users/1。

这样,请求就可以真正的交给 HttpURLConnection或者开发者自定义的客户端,以 HttpURLConnection 为例,MethodHandler 会按以下步骤执行请求:

  1. 创建 HTTP 连接:根据最终 URL 和 HTTP 方法(如 GET)打开连接。

  2. 设置请求头:添加默认头信息(如 Content-Type: application/json)和自定义头(通过 @RequestHeader 注解指定)。

  3. 处理请求参数

    • 路径参数(如 {id})已在 RequestTemplate 中替换为实际值(如 1)。

    • 请求参数(@RequestParam)会被拼接在 URL 后(如 ?name=xxx)。

    • 请求体(@RequestBody)会被序列化为 JSON 或表单格式,写入连接的输出流。

  4. 发送请求并获取响应:调用 connection.getInputStream() 发送请求,同时获取服务端返回的响应流。

发送过去就完成了吗?别忘了,我们不是发送请求到浏览器,而是发送到服务,进行服务间调用

所以,需要经历一个从 HTTP 响应到 Java 对象的过程

服务端返回的响应是原始的 HTTP 报文(包含状态码、响应头、响应体),Feign 需要将其转换为开发者定义的返回值类型(如 User)。这一过程由 Decoder 解码器完成,核心步骤如下:

  1. 校验状态码:若状态码为 4xx(客户端错误)或 5xx(服务端错误),Feign 会抛出 FeignException(可通过 @ErrorHandler 自定义异常处理)。
  2. 解析响应体:将响应体的流数据(如 JSON 字符串)通过消息转换器(如 Jackson)反序列化为目标对象(如 User)。例如,将 {“id”:1,“name”:“Alice”} 转换为 User(id=1, name=“Alice”)。
  3. 返回结果:将解析后的对象作为方法调用的返回值,传递给开发者的代码。

Feign 请求发送的流程可概括为一条清晰的协作链

1
2
3
开发者调用接口方法 → 动态代理拦截调用 → MethodHandler 接收参数 → 
负载均衡解析服务地址 → HTTP 客户端发送请求 → 服务端处理并返回响应 →
解码器转换响应为对象 → 结果返回给开发者

这一过程中,Feign 通过动态代理解耦了接口定义与实际通信逻辑,通过第三方客户端实现了 HTTP 协议的适配,通过负载均衡和重试机制保证了分布式环境下的可靠性。开发者无需关注底层细节,只需专注于接口设计,极大简化了远程调用的开发成本。

在发送请求中的机制如何进行

发送请求的过程不一定一帆风顺,如果出现问题,就要正确处理,其中若请求因网络波动等临时原因失败,Feign 可通过重试机制自动重试。重试逻辑由 Retryer 接口实现,默认策略如下:

  • 初始重试间隔为 100 毫秒,每次重试间隔翻倍(最多 1 秒)。
  • 最大重试次数为 5 次(包含首次请求)。
  • 仅对 GET 请求重试(默认不重试 POST/PUT 等可能修改数据的请求,避免重复提交)。

我们可以通过 Feign.Builder 自定义重试策略,其中这个 Builder 贼重要,Feign.builder() 会创建一个 Builder 类,用来创建 Feign,这个 Builder 类描述了 Feign 的各个关键组件,比如 Contract、Client、Retryer 等等,Retryer 只是其中不太重要的部分,这个自定义就涉及到 Builder 来替换个别关键组件

1
2
3
Feign.builder()
.retryer(new Retryer.Default(100, 1000, 3)) // 间隔100ms起步,最大1秒,最多重试3次
.target(UserServiceClient.class, "http://user-service");

Fegin 底层的请求重试机制,也依赖 LoadBalancer 的底层来实现,所以这里我就不再细讲了

Fegin 的整体运行流程的源码分析

Fegin 是如何在启动时候扫描包的

首先,通过应用启动类上的@EnableFeignClients注解开启 Feign 的装配和远程代理实例创建。这一步是告诉 Spring,我需要使用 Fegin,此时系统会自动启动一个 FeignClientsRegistrar它的任务是扫描所有标了@FeignClient的接口。

image-20250723105855544

我们可以看到,@EnableFeignClients注解的源码会导入FeignClientsRegistrar类,我们进入这个类看一下

1
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware 

FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,这意味着它能在 Spring 启动时通过 @Import 触发,主动向容器中注册 Bean 定义。剩下那俩我看了,一个是获取资源加载器,它主要是扫描类路径,一个是获取配置占位符等环境变量。

我们来看FeignClientsRegistrar 的入口方法是 registerBeanDefinitions,该方法会依次执行 注册默认配置扫描并注册 Feign 客户端 两个核心操作。

1
2
3
4
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}

注册默认配置就是通过调用registerDefaultConfiguration方法实现,源码和实现逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}

this.registerClientConfiguration(registry, name, "default", defaultAttrs.get("defaultConfiguration"));
}
}

当使用 @EnableFeignClients 注解时,若指定了 defaultConfiguration 属性(全局默认配置),该方法会将其注册为 FeignClientSpecification 类型的 Bean,用于统一配置所有 Feign 客户端的公共参数(如编码器、解码器等)。

它会从 @EnableFeignClients 中提取 defaultConfiguration 属性,生成唯一的配置名称(格式为 default.主启动类全类名),并注册为 FeignClientSpecification 实例(用于后续 Feign 客户端的配置合并)。

接着registerFeignClients方法被调用,这个方法就是真正实现 扫描 @FeignClient 接口,它会负责扫描所有标注 @FeignClient 的接口,并为每个接口生成对应的 Bean 定义。

接下来我会拆解这个方法进行讲解

首先创建一个 LinkedHashSet 用于存储所有可能的候选接口(BeanDefinition 类型),LinkedHashSet 保证了元素唯一且有序。

1
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet();

然后从启动类上的 @EnableFeignClients 注解中提取属性(如 clientsbasePackages 等),用于确定扫描范围:

1
2
3
4
// 获取 @EnableFeignClients 注解的所有属性(如 clients、basePackages 等)
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 提取 "clients" 属性(显式指定的 Feign 客户端接口数组)
Class<?>[] clients = attrs == null ? null : (Class[])attrs.get("clients");

@EnableFeignClients 注解中与扫描范围相关的核心属性如下,这些属性都是可以进行指定的:

  • clients:显式指定哪些接口是 Feign 客户端(直接指定类,无需扫描);
  • basePackages:指定扫描的包路径;
  • basePackageClasses:通过类所在包间接指定扫描路径;
  • 若以上都未指定,默认扫描启动类所在的包。

根据 clients 属性是否有值,分为显式指定包路径扫描两种模式:

显式指定模式(clients 属性有值):如果 @EnableFeignClients(clients = {UserClient.class, OrderClient.class}) 显式指定了接口,则直接将这些接口添加到候选集合中,无需扫描,这种模式适用于精确指定少数几个 Feign 客户端的场景,避免全包扫描的性能开销。

1
2
3
4
5
6
if (clients != null && clients.length != 0) {
for(Class<?> clazz : clients) {
// 为每个显式指定的类创建 BeanDefinition(描述 Bean 的元信息)
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}

包路径扫描模式(clients): 属性无值如果未显式指定 clients,则通过类路径扫描寻找所有标注 @FeignClient 的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
else {
// 创建类路径扫描器
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
// 设置资源加载器(用于读取类路径资源)
scanner.setResourceLoader(this.resourceLoader);
// 添加过滤器:只保留标注了 @FeignClient 的类
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));

// 获取扫描的基础包路径(从 @EnableFeignClients 注解中解析)
for(String basePackage : this.getBasePackages(metadata)) {
// 扫描指定包下的所有候选组件,并添加到集合中
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
  • 扫描器(getScanner():自定义的 ClassPathScanningCandidateComponentProvider,仅扫描独立接口(排除内部类、注解、抽象类等);
  • 过滤器(AnnotationTypeFilter:确保只收集标注了 @FeignClient 的接口;
  • 基础包路径(getBasePackages:解析 @EnableFeignClientsbasePackagesbasePackageClasses 等属性,若未指定则默认使用启动类所在包。

之后继续遍历所有候选接口,验证其合法性,并为每个符合条件的接口注册 Feign 客户端相关的 Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
for(BeanDefinition candidateComponent : candidateComponents) {
// 候选组件必须是带注解的 Bean 定义(如 @FeignClient 标注的接口)
if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
// 获取接口的注解元数据(如类名、标注的注解等)
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();

// 通过 `Assert.isTrue` 确保 `@FeignClient` 只标注在接口上
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

// 提取 @FeignClient 注解的属性(如 name、url、fallback 等)
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());

// 获取客户端名称(从 @FeignClient 的 name/value/serviceId 中解析)
String name = this.getClientName(attributes);
// 获取接口全类名
String className = annotationMetadata.getClassName();

// 1. 注册客户端的配置类(如 @FeignClient(configuration = XxxConfig.class))
this.registerClientConfiguration(registry, name, className, attributes.get("configuration"));

// 2. 注册 Feign 客户端代理 Bean(核心步骤,生成可被注入的代理对象)
this.registerFeignClient(registry, annotationMetadata, attributes);
}
}
  • 合法性验证:通过 Assert.isTrue 确保 @FeignClient 只标注在接口上(Feign 基于接口生成代理);
  • 解析 @FeignClient 属性:提取服务名、URL、降级类、配置类等核心配置;
  • 注册配置registerClientConfiguration 将客户端的个性化配置注册为 FeignClientSpecification 类型的 Bean(用于后续 Feign 客户端构建);
  • 注册 Feign 代理registerFeignClient 核心逻辑,通过 FeignClientFactoryBean 生成接口的代理对象,并注册到 Spring 容器中,最终可被 @Autowired 注入使用。

Fegin 是如何创建对应接口的代理对象的

此时,通过为主启动类标注@EnableFeignClients注解,通过FeignClientsRegistrar类扫描完了标注@FeginClient注解的接口,然后然后为它创建一个 FactoryBean,并存入 Spring IOC 容器中

当你服务的某些位置需要注入上面定义的 Fegin 接口的实例,但是这个接口是空的,你就往上面加了个注解,别的内容也没有。所以,我们实际上是使用的这个接口是 Fegin 创建的动态代理对象,Spring会通过注册的 FactoryBean 工厂类实例的 getObject()方法获取接口的动态代理实例。

进入我们上面的这个方法registerFeignClients中最后实现注册 Feign 客户端代理调用的registerFeignClient方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 根据配置决定是否延迟注册 FeignClient 
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 获取当前类的完整类名(通常是标注了 @FeignClient 注解的类)
String className = annotationMetadata.getClassName();

// 判断配置项 spring.cloud.openfeign.lazy-attributes-resolution 是否为 false
// 如果为 false(默认值也是 false),则表示“非延迟加载”,立即注册 Bean 定义
if (String.valueOf(false).equals(this.environment.getProperty(
"spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {

// 立即注册 FeignClient Bean 定义
this.eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
} else {
// 否则采用懒加载方式注册 FeignClient Bean 定义
this.lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
}
}

这个方法的核心,就是其中两个根据配置决定是“延迟”还是“立即”注册一个 Feign Client 接口,生成其代理对象,我们就挑选懒加载的那个方法,顺便来简单学习一下 feign 的懒加载机制,可以看到这个方法真大啊

image-20250723160208695

首先,这个方法一上来就准备了一堆的基础参数

1
2
3
4
5
6
7
8
// 获取 Spring 的 bean 工厂(用于后续的属性解析)
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory)registry : null;
// 解析 Feign 接口的类对象(例如我们定义的 UserServiceFeign 接口)
Class clazz = ClassUtils.resolveClassName(className, (ClassLoader)null);
// 获取 contextId(用于区分不同的 Feign 客户端,避免冲突)
String contextId = this.getContextId(beanFactory, attributes);
// 获取 Feign 客户端的名称(通常是 @FeignClient 注解的 name/value 属性)
String name = this.getName(attributes);

然后就是创建 FeignClientFactoryBean,用于创建 Fegin 接口的代理对象,可以看到设置方式是用的 setter

1
2
3
4
5
6
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz); // 设置目标接口类型(我们定义的 Feign 接口)
factoryBean.setRefreshableClient(this.isClientRefreshEnabled()); // 是否支持动态刷新

接下来这个方法继续进行定义BeanDefinition(bean 的元数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
// 为 factoryBean 设置更多属性(从 @FeignClient 注解中解析)
factoryBean.setUrl(this.getUrl(beanFactory, attributes)); // 服务地址(如 http://user-service)
factoryBean.setPath(this.getPath(beanFactory, attributes)); // 统一路径前缀
factoryBean.setDismiss404(...); // 是否忽略 404 错误

// 处理降级逻辑(fallback 和 fallbackFactory)
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(...); // 设置降级类
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(...); // 设置降级工厂
}

// 核心:通过 factoryBean 创建 Feign 代理对象
return factoryBean.getObject();
});

BeanDefinitionBuilder 用于构建 Spring bean 的元数据(BeanDefinition)。注意不要混淆,这里构建的数据是 Feign 接口类型,创建逻辑等内容,还没到构建请求这一步呢

把接口注册为动态代理的核心内容就在这里,这里通过 lambda 表达式定义了 bean 的创建逻辑:最终通过 factoryBean.getObject() 生成 Feign 接口的代理对象。内部流程如下:

  1. 根据配置(如 @FeignClient 的属性、全局 Feign 配置)构建 Feign 客户端。
  2. 通过 Feign 框架的 Targeter 生成目标接口的代理对象(基于 JDK 动态代理或 CGLIB)。
  3. 代理对象会将接口方法调用转换为 HTTP 请求(如 @GetMapping("/users/{id}") 转换为 GET 请求),并通过负载均衡(如 Ribbon)调用目标服务。

接下来就是 feign 涉及到懒加载的机制了,就是通过这段配置 BeanDefinition 的属性来实现的

1
2
3
4
5
definition.setAutowireMode(2); // 设置自动装配模式(2 表示按类型装配)
definition.setLazyInit(true); // 延迟初始化(在第一次使用时才创建代理对象)
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean); // 存储 factoryBean 引用
beanDefinition.setPrimary((Boolean)attributes.get("primary")); // 是否为主要 bean(解决依赖冲突)

Bean 的元数据都被注册并且配置完成了,接下来就是向 Spring IOC 容器中注册

1
2
3
4
// 构建 bean 的持有者(包含 bean 定义、名称、别名)
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
// 注册到 Spring 容器
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
  • 此时 Spring 容器中就有了该 Feign 接口的 bean 定义,但尚未创建实例(因为设置了延迟初始化)。
  • 当其他组件通过 @Autowired 注入该接口时,Spring 会触发 factoryBean.getObject() 生成代理对象。

刷新一下,为了支持 Feign 客户端的动态配置刷新(如通过配置中心修改服务地址后无需重启)。

1
2
3
// 注册用于动态刷新的 bean(如请求配置、URL 刷新器)
this.registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
this.registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);

我们看到,上面在注册 Feign Client 的Bean 定义的时候,会factoryBean.getObject()用来注册,进入到这个方法中

会发现实际调用的是 FeignClientFactoryBean中的this.getTarget();方法,这个就是 Feign 注册Client的Bean工厂

image-20250725084010583

进入这个方法, getTarget() 方法是该类中最核心的方法之一,用于构建并返回 Feign 接口的动态代理实例,也就是实际用于发起 RPC 调用的对象,该方法的主要作用是,根据 @FeignClient 注解的配置(已经被包装在其中,如 urlnamepath 等),结合 Spring 容器中的组件(如负载均衡器、客户端配置等),创建 Feign 代理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<T> T getTarget() {
// FeignClientFactory 是 Spring 管理的工厂类,用于获取 Feign 客户端所需的配置
FeignClientFactory feignClientFactory = this.beanFactory != null ? (FeignClientFactory)this.beanFactory.getBean(FeignClientFactory.class) : (FeignClientFactory)this.applicationContext.getBean(FeignClientFactory.class);

// 创建 Feign 构建器,this.feign(feignClientFactory) 方法会根据配置初始化 Feign.Builder,设置编码器(Encoder)、解码器(Decoder)、日志器(Logger)等核心组件。
Feign.Builder builder = this.feign(feignClientFactory);

// 无固定 URL(依赖负载均衡)
if (!StringUtils.hasText(this.url) && !this.isUrlAvailableInConfig(this.contextId)) {
if (LOG.isInfoEnabled()) {
LOG.info("For '" + this.name + "' URL not provided. Will try picking an instance via load-balancing.");
}
// 1. 补全 URL 格式(默认添加 http 协议)
if (!this.name.startsWith("http://") && !this.name.startsWith("https://")) {
this.url = "http://" + this.name;
} else {
this.url = this.name;
}

String var10001 = this.url;
// 2. 拼接路径(结合 @FeignClient 的 path 属性)
this.url = var10001 + this.cleanPath();
// 3. 通过负载均衡创建代理对象,loadBalance()结合负载均衡器选择服务实例,生成可动态路由的代理对象。
return (T)this.loadBalance(builder, feignClientFactory, new Target.HardCodedTarget(this.type, this.name, this.url));
} else {
// 1. 补全 URL 协议(若未指定 http/https)
if (StringUtils.hasText(this.url) && !this.url.startsWith("http://") && !this.url.startsWith("https://")) {
this.url = "http://" + this.url;
}
// 2. 配置 Feign 客户端(如 HTTP 客户端实现)
Client client = (Client)this.getOptional(feignClientFactory, Client.class);
if (client != null) {
// 处理负载均衡客户端的代理(剥离负载均衡逻辑,直接使用底层 HTTP 客户端)
if (client instanceof FeignBlockingLoadBalancerClient) {
client = ((FeignBlockingLoadBalancerClient)client).getDelegate();
}

if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
client = ((RetryableFeignBlockingLoadBalancerClient)client).getDelegate();
}

builder.client(client); // 设置 HTTP 客户端(如 OkHttp、HttpClient 等)
}
// 3. 应用自定义配置
this.applyBuildCustomizers(feignClientFactory, builder);
// 4. 创建代理对象,核心,resolveTarget()解析目标服务的 URL(处理配置文件中的占位符等)
Targeter targeter = (Targeter)this.get(feignClientFactory, Targeter.class);
return (T)targeter.target(this, builder, feignClientFactory, this.resolveTarget(feignClientFactory, this.contextId, this.url));
}
}

我们可以看到,在处理 url 配置的时候,Feign 客户端有两种使用方式:通过服务名(结合负载均衡)调用通过固定 URL 调用getTarget() 方法会根据配置自动区分这两种场景。

  • !StringUtils.hasText(this.url) && !this.isUrlAvailableInConfig(this.contextId),无固定 URL(依赖负载均衡),会在@FeignClient 未指定 url 属性,依赖服务名(如 name = "user-service")通过注册中心(如 Eureka/Nacos)发现服务实例。
    • 核心逻辑
      • 将服务名转换为基础 URL(如 user-servicehttp://user-service)。
      • 调用 loadBalance() 方法,结合负载均衡器(如 Spring Cloud LoadBalancer)选择具体服务实例,最终创建代理对象。
  • 有固定 URL(直接调用),当@FeignClient 指定了 url 属性(如 url = "http://localhost:8080"),直接调用固定地址。
    • 核心逻辑
      • 补全 URL 协议(确保格式正确)。
      • 配置底层 HTTP 客户端(如默认的 HttpClient 或自定义的 OkHttp)。
      • 通过 Targeter 接口创建代理对象(默认实现为 DefaultTargeter,最终调用 Feign 原生的 ReflectiveFeign 生成代理)。

而到这里,FeignClientFactoryBean 类通过 getTarget() 方法最终生成 Feign 接口的动态代理(由 Feign 原生的 ReflectiveFeign 实现),该代理会拦截接口方法调用,转化为 HTTP 请求。

FeignClientFactoryBean 本质上是 Spring 对 Feign 的封装,最终会调用 Feign 原生的 API 生成代理:

  • 通过 Feign.Builder 构建 Feign 客户端。
  • 通过 Targeter.target() 方法触发 ReflectiveFeign.newInstance(),生成包含 InvocationHandlerMethodHandler 的代理对象(即你之前关注的动态代理和方法处理器逻辑)。

因此,getTarget() 方法是 Spring 与 Feign 原生逻辑的「衔接点」,负责将 Spring 配置转化为 Feign 可识别的参数,最终生成可用的 RPC 代理对象。

也是在这里,集成了Spring 的负载均衡(LoadBalancer)、配置管理(Environment)、依赖注入(BeanFactory)等功能,使 Feign 无缝融入 Spring 应用。

Feign如何构建请求

我们知道,Feign 不需要手写各种请求头的编写逻辑,那么 Feign 是的完整请求是如何构建的

在创建 Feign 接口的动态代理实例时,我们知道 Feign 会为每一个被注解标记的RPC接口创建一个调用处理器 InvocationHandler,也会为接口的每一个RPC方法创建一个方法处理器MethodHandler,并且将方法处理器缓存在调用处理器InvocationHandlerdispatch映射成员中。

Targeter.target(),,默认 Targeter 实现为 DefaultTargeter,它会调用 Feign.Builder.target() 方法,最终由 Feign 原生的 ReflectiveFeign 生成代理对象,我们进入到 ReflectiveFeign 类中,最终会进入到这个方法

image-20250725085416833

ReflectiveFeign.newInstance(),这是 Feign 生成动态代理的入口,会创建 InvocationHandlerMethodHandler,并关联到代理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 创建Feign接口的动态代理实例,并绑定请求处理逻辑
* 该方法是Feign动态代理机制的核心入口,负责将接口方法与请求处理器关联
*
* @param target 目标接口的元信息封装(包含接口类型、服务名、URL等)
* @param requestContext 请求上下文(包含配置、拦截器等全局信息)
* @param <T> 接口类型
* @return 接口的动态代理实例,调用该实例的方法会触发HTTP请求
*/
public <T> T newInstance(Target<T> target, C requestContext) {
// 验证目标接口的合法性(确保是接口类型,且符合Feign规范)
ReflectiveFeign.TargetSpecificationVerifier.verify(target);

// 为接口的每个方法创建对应的请求处理器(MethodHandler)
// 核心逻辑:解析接口注解生成请求元信息,并绑定到具体的方法处理器
Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler =
this.targetToHandlersByName.apply(target, requestContext);

// 创建调用处理器(InvocationHandler),关联方法与处理器的映射关系
// 当代理对象的方法被调用时,会由该处理器路由到对应的MethodHandler
InvocationHandler handler = this.factory.create(target, methodToHandler);

// 生成JDK动态代理对象(实现目标接口),并绑定调用处理器
T proxy = (T) Proxy.newProxyInstance(
target.type().getClassLoader(), // 类加载器
new Class[]{target.type()}, // 代理实现的接口
handler // 方法调用拦截器
);

// 处理默认方法(Java 8接口默认方法),将其绑定到代理实例
for (InvocationHandlerFactory.MethodHandler methodHandler : methodToHandler.values()) {
if (methodHandler instanceof DefaultMethodHandler) {
((DefaultMethodHandler) methodHandler).bindTo(proxy);
}
}

return proxy; // 返回最终可用的Feign代理对象
}

该方法是 Feign 生成代理对象的核心逻辑,也是后续请求构建的基础。

我们也知道,在创建动态代理实例时,Feign也会通过RPC方法的注解为每一个RPC方法生成一个RequesTemplate请求模板实例,RequestTemplate中包含请求的所有信息,如请求URL、请求类型(如GET)、请求参数等。

我们知道,上面 newInstance 方法中,调用了Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = this.targetToHandlersByName.apply(target, requestContext);,为代理对象创建methodToHandler,而targetToHandlersByNameParseHandlersByName的实例,调用 ParseHandlersByNameapply 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 为Feign接口的每个方法创建对应的处理器(MethodHandler),建立方法与处理器的映射关系
* 作用:解析接口方法的元信息,绑定到具体的请求处理器,为后续代理调用提供路由依据
*/
public Map<Method, InvocationHandlerFactory.MethodHandler> apply(Target target, C requestContext) {
// 存储方法与处理器的映射关系,使用LinkedHashMap保证迭代顺序与方法定义顺序一致
Map<Method, InvocationHandlerFactory.MethodHandler> result = new LinkedHashMap();

// 1. 处理Feign接口中定义的业务方法(非Object类继承的方法)
// 通过Contract解析接口上的注解,生成每个方法的元信息(MethodMetadata)
for (MethodMetadata md : this.contract.parseAndValidateMetadata(target.type())) {
Method method = md.method(); // 获取当前方法对象
// 过滤掉Object类的方法(如toString、hashCode等,这些由FeignInvocationHandler直接处理)
if (method.getDeclaringClass() != Object.class) {
// 为当前方法创建对应的处理器(默认是SynchronousMethodHandler)
InvocationHandlerFactory.MethodHandler handler = this.createMethodHandler(target, md, requestContext);
// 将方法与处理器的映射存入结果集
result.put(method, handler);
}
}

// 2. 处理接口中的默认方法(Java 8接口默认方法)
for (Method method : target.type().getMethods()) {
if (Util.isDefault(method)) { // 判断是否为默认方法(有方法体的接口方法)
// 创建默认方法处理器,用于调用接口默认实现
InvocationHandlerFactory.MethodHandler handler = new DefaultMethodHandler(method);
result.put(method, handler);
}
}

// 返回方法与处理器的完整映射,供InvocationHandler路由使用
return result;
}

ParseHandlersByNameReflectiveFeign 的内部类,负责为每个接口方法创建对应的 MethodHandler(默认是 SynchronousMethodHandler),它调用了 createMethodHandler(...) 创建 SynchronousMethodHandler,并将 MethodMetadata 传入该处理器。

1
2
3
4
5
private InvocationHandlerFactory.MethodHandler createMethodHandler(Target<?> target, MethodMetadata md, C requestContext) {
return md.isIgnored() ? (args) -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
} : this.factory.create(target, md, requestContext); // 调用工厂创建处理器
}

看到 create 方法,我们一个劲的进入,发现

1
2
3
4
5
6
public InvocationHandlerFactory.MethodHandler create(Target<?> target, MethodMetadata md, Object requestContext) {
// 这里创建 RequestTemplate
RequestTemplate.Factory buildTemplateFromArgs = this.requestTemplateFactoryResolver.resolve(target, md);
// 这里初始化 buildTemplateFromArgs
return new SynchronousMethodHandler(target, this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, md, buildTemplateFromArgs, this.options, this.responseHandler, this.propagationPolicy);
}

工厂类(默认 SynchronousMethodHandler.Factory)在创建 SynchronousMethodHandler 时,会将 MethodMetadata 作为参数传入,并初始化 buildTemplateFromArgs(用于后续构建 RequestTemplate),而BuildTemplateByResolvingArgs 是专门用于根据 MethodMetadata 和方法参数构建 RequestTemplate 的工具类。

所以说,RequestTemplate 并非在代理创建时直接生成,而是在方法被调用时,根据 MethodMetadata 和实际参数动态构建。实现逻辑在 SynchronousMethodHandlerinvoke 方法中,通过 BuildTemplateByResolvingArgs 完成。

当通过代理对象调用接口方法时,调用会被 FeignInvocationHandler 拦截,路由到对应的 SynchronousMethodHandler

image-20250725094539822
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 当代理对象调用接口方法时,此方法会被触发
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"equals".equals(method.getName())) {
... 匹配逻辑省略
// 从 dispatch 映射中找到对应的 MethodHandler(即 SynchronousMethodHandler)
return ((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(args);
}
} else {
try {
// 备选方法
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}
1
2
3
4
5
6
7
8
// 调用上述InvocationHandlerFactory中的MethodHandler中的invoke
public interface MethodHandler {
Object invoke(Object[] var1) throws Throwable;

public interface Factory<C> {
MethodHandler create(Target<?> var1, MethodMetadata var2, C var3);
}
}

SynchronousMethodHandlerinvoke 方法是请求构建的核心,会调用 buildTemplateFromArgs.create() 生成 RequestTemplate

1
2
3
4
5
6
7
public Object invoke(Object[] argv) throws Throwable {
// 根据方法参数(argv)和 MethodMetadata 构建 RequestTemplate
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
// 应用拦截器、发送请求等后续操作
Request.Options options = this.findOptions(argv);
Retryer retryer = this.retryer.clone();
..........

到这里就结合 MethodMetadata(静态注解信息)和方法实际参数(argv),动态生成包含完整请求信息的 RequestTemplate

Feign如何发送请求

那么,请求是如何发送的

上面,我们知道在SynchronousMethodHandler类中成功构建请求模板,到这里,RequestTemplate 包含请求的所有静态和动态信息(如 http://user-service/users/1GET 方法、请求头)。

而继续看SynchronousMethodHandler类,上面 invoke 方法会调用一个 executeAndDecode 类,这个类会调用targetRequest,这个方法会生成 HTTP 请求对象

image-20250725095403420
1
2
3
4
5
6
7
8
Request targetRequest(RequestTemplate template) {
// 执行所有 RequestInterceptor(如添加 Authorization 头)。
for(RequestInterceptor interceptor : this.requestInterceptors) {
interceptor.apply(template);
}

return this.target.apply(template);
}

而且会将 RequestTemplate 转换为 Request(包含最终 URL、请求方法、请求体字节流)。

之后,SynchronousMethodHandler.executeAndDecode()调用底层 HTTP 客户端发送请求

image-20250725095755148

可以在这里找到默认客户端是 HttpURLConnectionClient

image-20250725100123219

这个类直接基于 JDK 自带的 HttpURLConnection 实现 HTTP 通信,是 Feign 原生的默认客户端

image-20250725100313788

而 Spring Cloud 通过 LoadBalancerFeignClient 包装默认客户端,实现服务发现与负载均衡,核心逻辑如下:

此时 SynchronousMethodHandler 中的 client 实际是 LoadBalancerFeignClient,其 execute 方法会先通过负载均衡器获取服务实例,再调用底层客户端发送请求:

image-20250725100526661

其中的 private final Client delegate; 就是底层实际客户端(如 HttpURLConnectionClient),可以看到在下面被换成了 LoadBalancerFeignClient,欸我草 Spring Cloud 学长这招太狠了

与熔断(Circuit Breaker,如 Resilience4j/Sentinel)的结合类似,都是包装 SynchronousMethodHandler 或添加拦截器实现,只不过这个是当请求失败时触发熔断逻辑。

要理解 Feign 如何构建并且发送请求,我们从代理对象创建方法调用拦截请求构建发送三个阶段展开,这也是总结一下:

  • 阶段一:动态代理对象的创建(当前方法的核心作用)

    Feign 通过 JDK 动态代理为接口生成代理对象,关键逻辑如下:

    • Target 元信息封装,包含接口的核心信息:
    • methodToHandler 映射构建,这是请求构建的核心前置步骤,通过 this.targetToHandlersByName.apply(...) 完成
    • InvocationHandler 路由绑定,创建 FeignInvocationHandler(调用处理器),内部维护 methodToHandler 映射(即 dispatch 成员)。当代理对象的方法被调用时,处理器会根据方法找到对应的 MethodHandler 并执行。
  • 阶段二:方法调用拦截与请求构建(SynchronousMethodHandler

    当通过代理对象调用接口方法时,会触发 FeignInvocationHandler.invoke(...),最终调用 SynchronousMethodHandler.invoke(...) 完成请求构建

    • 构建 RequestTemplate(请求模板)RequestTemplate template = buildTemplateFromArgs.create(argv);,这是SynchronousMethodHandler 中的核心逻辑,它会根据 MethodMetadata 和方法参数(argv)生成包含完整请求信息的模板。
    • 应用请求的拦截器,Request request = targetRequest(template);,通过 RequestInterceptor 对请求进行全局修改(如添加统一 Header、签名等)。
    • 发送 http 请求,Response response = client.execute(request, options);,将 Request 转换为实际的 HTTP 请求并发送到服务端。
  • 阶段三:响应处理与结果返回

    return decode(response);,通过 Decoder 将 HTTP 响应体(如 JSON 字符串)转换为接口方法的返回类型(如 User 对象)。

所以在这个类中,Feign 请求构建的完整链路如下

  1. 代理创建:通过 newInstance() 生成接口代理,绑定 MethodHandler 与方法的映射。
  2. 方法调用:代理对象的方法被调用时,FeignInvocationHandler 路由到对应的 SynchronousMethodHandler
  3. 请求构建SynchronousMethodHandler 解析参数生成 RequestTemplate,应用拦截器后通过 HTTP 客户端发送请求。
  4. 响应处理:解码响应体并返回结果(或转换异常)。