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
private RestTemplate restTemplate;
// 调用远程服务
public User getUserById(Long id) {
// 手动拼接 URL(可能需要硬编码服务地址)
String url = "http://user-service/users/" + id;
// 手动指定请求方法(GET)和返回类型
return restTemplate.getForObject(url, User.class);
}- 代码冗余:每次调用都需要编写 URL 拼接、请求方法指定等重复代码;
- 服务地址硬编码:如果服务地址变化(如使用服务注册中心动态获取地址),需要额外处理;
- 集成性差:与 Spring Cloud 生态组件(如负载均衡 Ribbon、熔断 Hystrix)的集成需要手动配置;
- 可读性低:大量模板代码掩盖了业务逻辑,不利于维护。
Feign
是声明式的客户端,通过接口和注解自动生成请求实现。是 Spring Cloud 官方推荐的服务调用工具,它基于 “声明式” 思想,解决了RestTemplate
的上述问题1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 声明 Feign 客户端(指定要调用的服务名)
// 服务名从注册中心获取
public interface UserServiceClient {
// 声明接口方法(与远程服务的接口保持一致)
User getUserById(; Long id)
}
// 使用时直接注入接口,像调用本地方法一样使用
private UserServiceClient userClient;
public User getUser(Long id) {
return userClient.getUserById(id); // 无需关心 HTTP 细节
}- Feign 只需定义接口并添加注解,无需编写实现逻辑,底层会自动生成 HTTP 请求代码
- Feign 天生为 Spring Cloud 设计,无需额外配置即可集成核心组件
- Feign 接口的方法定义与远程服务的 Controller 接口完全一致
我们再看一下我们之前利用 RestTemple 发起远程调用的代码
1 |
|
这样存在下面的问题:
- 代码可读性太差,而且容易造成多人多思路的差异,业务逻辑被掩盖
- 参数复杂,url 维护不方便
- 与微服务生态集成繁琐,若要添加负载均衡、熔断等能力,需要额外编写代码:
同样的场景,用 Feign 实现,看看 Fegin 是如何使用声明式来优雅解决上述问题
1 | // 1. 定义 Feign 接口(专注于接口声明,与远程服务保持一致) |
- 声明式编程,聚焦业务
- 参数与接口强绑定,易维护
- 零配置集成微服务能力
所以说,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()
生成接口的代理实例。
生成动态代理由FeignClientFactoryBean
与
ReflectiveFeign
创建远程调用的动态代理实例。
如果应用某些地方需要注入RPC接口的实例(比如被@Resource引用),Spring就会通过注册的FactoryBean工厂类实例的getObject()方法获取RPC接口的动态代理实例。
通过代理对象调用接口方法时,Feign 会拦截调用,完成请求构建、负载均衡、HTTP 发送等流程。
在创建RPC接口的动态代理实例时,Feign会为每一个RPC接口创建一个调用处理器
InvocationHandler
,也会为接口的每一个RPC方法创建一个方法处理器MethodHandler
,并且将方法处理器缓存在调用处理器InvocationHandler
的dispatch
映射成员中。
在创建动态代理实例时,Feign也会通过RPC方法的注解为每一个RPC方法生成一个RequesTemplate
请求模板实例,RequestTemplate
中包含请求的所有信息,如请求URL、请求类型(如GET)、请求参数等。

之后发生RPC调用时,通过动态代理实例类完成远程的HTTP调用。
SynchronousMethodHandler
是处理请求的核心,负责将方法调用转换为 HTTP 请求,通过
buildTemplateFromArgs.create(args)
结合
MethodMetadata
(静态注解信息)和方法参数(动态值),生成包含完整请求信息的
RequestTemplate
之后MethodHandler
会结合实际的调用参数,通过RequesTemplate
模板实例生成Request
请求实例。最后,将Request
请求实例交给feign.Client
客户端实例进一步完成HTTP请求处理。
在完成远程HTTP调用前需要进行客户端负载均衡的处理。
Fegin 如何实现远程调用
这个图展示了 Feign 框架实现服务调用的原理,它根据被 Fegin 注解标记的接口,使用动态代理生成动态代理类,我们使用 Fegin 生成的代理类构建并且发送了 HTTP 请求并得到响应结果。

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

Feign远程调用的工作完整流程
Fegin 的请求如何构建
Feign 根据我们编写的 interface 动态生成一个实现了这个 interface 的代理实例,我们的代码就是通过这个代理实例实现了真正的远程调用 API 的逻辑。我们来分析看看 Feign 是如何生成这个代理实例的并且实现服务间的调用的。
这种设计离不开 Feign
的核心思想,通过接口声明定义远程调用的契约,再通过动态代理将接口方法转换为实际的
HTTP 请求。开发者只需定义接口并添加注解(如
@FeignClient
),Feign 会自动处理参数封装、URL
映射、请求发送和响应解析等工作。
首先,Spring 启动时,Feign 会扫描带有 @FeignClient
注解的接口。
对于每个被扫描到的接口,Feign 会通过 Feign.Builder
创建一个动态代理对象(JDK
动态代理,基于接口)。这个代理对象会被注入到 Spring
容器中,替代接口成为实际的调用者。
1 | // 开发者定义的Feign接口 |
这时候,上面我们定义的 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
,即该方法对应的专属处理器。
具体来说,InvocationHandler
的 invoke
方法会做两件关键事情:
- 确定当前调用的方法对应的
MethodHandler
(通过方法签名匹配)。 - 调用
MethodHandler.invoke(Object[] args)
方法,将方法参数(如 1L)传递给处理器,正式启动请求发送流程。 - 把参数发送到这里就是联系到上面的请求构建的部分了,
MethodHandler
会结合实际的调用参数,通过RequesTemplate
模板实例生成Request
请求实例。
之后,调用MethodHandler.invoke()
方法也不是意味着请求就发送过去了,而是把请求发送的任务交给了由MethodHandler
方法处理器,开始进行HTTP请求处理。

实际上 Feign 本身不直接实现 HTTP 通信,而是通过集成第三方 HTTP 客户端完成请求发送。默认情况下,Feign 使用 JDK 自带的 HttpURLConnection,但开发者可以通过配置切换为性能更优的客户端(如 OkHttp、HttpClient)。
Feign 通常与服务注册中心(如 Eureka、Nacos)配合使用,开发者在
@FeignClient
中指定的 name(如
user-service
)是服务名,而非具体的 IP:
端口。因此,请求发送前需要通过负载均衡器将服务名转换为实际的服务地址。
这一过程由 LoadBalancerFeignClient(Feign 整合 Spring Cloud LoadBalancer 或 Ribbon 的核心类)完成,具体逻辑参考我之前的文章,我只进行简单的说明,如下:
- 获取服务实例列表:负载均衡器会向注册中心查询 user-service 对应的所有可用服务实例(如 192.168.1.100:8080、192.168.1.101:8080)。
- 选择服务实例:根据负载均衡策略(如轮询、随机、权重等)从实例列表中挑选一个最优实例。例如,若采用轮询策略,可能选中 192.168.1.100:8080。
- 拼接完整 URL**:将服务实例的地址与之前解析的请求路径组合,生成最终的请求 URL。例如,http://192.168.1.100:8080/users/1。
这样,请求就可以真正的交给
HttpURLConnection
或者开发者自定义的客户端,以
HttpURLConnection 为例,MethodHandler 会按以下步骤执行请求:
创建 HTTP 连接:根据最终 URL 和 HTTP 方法(如 GET)打开连接。
设置请求头:添加默认头信息(如 Content-Type: application/json)和自定义头(通过 @RequestHeader 注解指定)。
处理请求参数:
路径参数(如 {id})已在 RequestTemplate 中替换为实际值(如 1)。
请求参数(@RequestParam)会被拼接在 URL 后(如 ?name=xxx)。
请求体(@RequestBody)会被序列化为 JSON 或表单格式,写入连接的输出流。
发送请求并获取响应:调用 connection.getInputStream() 发送请求,同时获取服务端返回的响应流。
发送过去就完成了吗?别忘了,我们不是发送请求到浏览器,而是发送到服务,进行服务间调用
所以,需要经历一个从 HTTP 响应到 Java 对象的过程
服务端返回的响应是原始的 HTTP
报文(包含状态码、响应头、响应体),Feign
需要将其转换为开发者定义的返回值类型(如 User)。这一过程由
Decoder
解码器完成,核心步骤如下:
- 校验状态码:若状态码为 4xx(客户端错误)或 5xx(服务端错误),Feign 会抛出 FeignException(可通过 @ErrorHandler 自定义异常处理)。
- 解析响应体:将响应体的流数据(如 JSON 字符串)通过消息转换器(如 Jackson)反序列化为目标对象(如 User)。例如,将 {“id”:1,“name”:“Alice”} 转换为 User(id=1, name=“Alice”)。
- 返回结果:将解析后的对象作为方法调用的返回值,传递给开发者的代码。
Feign 请求发送的流程可概括为一条清晰的协作链
1 | 开发者调用接口方法 → 动态代理拦截调用 → MethodHandler 接收参数 → |
这一过程中,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 | Feign.builder() |
Fegin 底层的请求重试机制,也依赖 LoadBalancer 的底层来实现,所以这里我就不再细讲了
Fegin 的整体运行流程的源码分析
Fegin 是如何在启动时候扫描包的
首先,通过应用启动类上的@EnableFeignClients
注解开启
Feign 的装配和远程代理实例创建。这一步是告诉 Spring,我需要使用
Fegin,此时系统会自动启动一个
FeignClientsRegistrar
它的任务是扫描所有标了@FeignClient
的接口。

我们可以看到,@EnableFeignClients
注解的源码会导入FeignClientsRegistrar
类,我们进入这个类看一下
1 | class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware |
FeignClientsRegistrar
实现了
ImportBeanDefinitionRegistrar
接口,这意味着它能在 Spring
启动时通过 @Import
触发,主动向容器中注册 Bean
定义。剩下那俩我看了,一个是获取资源加载器,它主要是扫描类路径,一个是获取配置占位符等环境变量。
我们来看FeignClientsRegistrar
的入口方法是
registerBeanDefinitions
,该方法会依次执行
注册默认配置 和 扫描并注册 Feign
客户端 两个核心操作。
1 | public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { |
注册默认配置就是通过调用registerDefaultConfiguration
方法实现,源码和实现逻辑如下
1 | private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { |
当使用 @EnableFeignClients
注解时,若指定了
defaultConfiguration
属性(全局默认配置),该方法会将其注册为
FeignClientSpecification
类型的 Bean,用于统一配置所有
Feign 客户端的公共参数(如编码器、解码器等)。
它会从 @EnableFeignClients
中提取
defaultConfiguration
属性,生成唯一的配置名称(格式为
default.主启动类全类名
),并注册为
FeignClientSpecification
实例(用于后续 Feign
客户端的配置合并)。
接着registerFeignClients
方法被调用,这个方法就是真正实现
扫描 @FeignClient
接口,它会负责扫描所有标注
@FeignClient
的接口,并为每个接口生成对应的 Bean 定义。
接下来我会拆解这个方法进行讲解
首先创建一个 LinkedHashSet
用于存储所有可能的候选接口(BeanDefinition
类型),LinkedHashSet
保证了元素唯一且有序。
1 | LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet(); |
然后从启动类上的 @EnableFeignClients
注解中提取属性(如
clients
、basePackages
等),用于确定扫描范围:
1 | // 获取 @EnableFeignClients 注解的所有属性(如 clients、basePackages 等) |
@EnableFeignClients
注解中与扫描范围相关的核心属性如下,这些属性都是可以进行指定的:
clients
:显式指定哪些接口是 Feign 客户端(直接指定类,无需扫描);basePackages
:指定扫描的包路径;basePackageClasses
:通过类所在包间接指定扫描路径;- 若以上都未指定,默认扫描启动类所在的包。
根据 clients
属性是否有值,分为显式指定和包路径扫描两种模式:
显式指定模式(clients
属性有值):如果
@EnableFeignClients(clients = {UserClient.class, OrderClient.class})
显式指定了接口,则直接将这些接口添加到候选集合中,无需扫描,这种模式适用于精确指定少数几个
Feign 客户端的场景,避免全包扫描的性能开销。
1 | if (clients != null && clients.length != 0) { |
包路径扫描模式(clients
): 属性无值如果未显式指定
clients
,则通过类路径扫描寻找所有标注
@FeignClient
的接口:
1 | else { |
- 扫描器(
getScanner()
):自定义的ClassPathScanningCandidateComponentProvider
,仅扫描独立接口(排除内部类、注解、抽象类等); - 过滤器(
AnnotationTypeFilter
):确保只收集标注了@FeignClient
的接口; - 基础包路径(
getBasePackages
):解析@EnableFeignClients
的basePackages
、basePackageClasses
等属性,若未指定则默认使用启动类所在包。
之后继续遍历所有候选接口,验证其合法性,并为每个符合条件的接口注册 Feign 客户端相关的 Bean:
1 | for(BeanDefinition candidateComponent : candidateComponents) { |
- 合法性验证:通过
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 | // 根据配置决定是否延迟注册 FeignClient |
这个方法的核心,就是其中两个根据配置决定是“延迟”还是“立即”注册一个 Feign Client 接口,生成其代理对象,我们就挑选懒加载的那个方法,顺便来简单学习一下 feign 的懒加载机制,可以看到这个方法真大啊

首先,这个方法一上来就准备了一堆的基础参数
1 | // 获取 Spring 的 bean 工厂(用于后续的属性解析) |
然后就是创建 FeignClientFactoryBean
,用于创建
Fegin
接口的代理对象,可以看到设置方式是用的 setter
1 | FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(); |
接下来这个方法继续进行定义BeanDefinition
(bean
的元数据)
1 | BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> { |
BeanDefinitionBuilder
用于构建 Spring bean
的元数据(BeanDefinition)。注意不要混淆,这里构建的数据是 Feign
接口类型,创建逻辑等内容,还没到构建请求这一步呢
把接口注册为动态代理的核心内容就在这里,这里通过 lambda 表达式定义了
bean 的创建逻辑:最终通过 factoryBean.getObject()
生成 Feign 接口的代理对象。内部流程如下:
- 根据配置(如
@FeignClient
的属性、全局 Feign 配置)构建 Feign 客户端。 - 通过 Feign 框架的
Targeter
生成目标接口的代理对象(基于 JDK 动态代理或 CGLIB)。 - 代理对象会将接口方法调用转换为 HTTP 请求(如
@GetMapping("/users/{id}")
转换为 GET 请求),并通过负载均衡(如 Ribbon)调用目标服务。
接下来就是 feign 涉及到懒加载的机制了,就是通过这段配置 BeanDefinition 的属性来实现的
1 | definition.setAutowireMode(2); // 设置自动装配模式(2 表示按类型装配) |
Bean 的元数据都被注册并且配置完成了,接下来就是向 Spring IOC 容器中注册
1 | // 构建 bean 的持有者(包含 bean 定义、名称、别名) |
- 此时 Spring 容器中就有了该 Feign 接口的 bean 定义,但尚未创建实例(因为设置了延迟初始化)。
- 当其他组件通过
@Autowired
注入该接口时,Spring 会触发factoryBean.getObject()
生成代理对象。
刷新一下,为了支持 Feign 客户端的动态配置刷新(如通过配置中心修改服务地址后无需重启)。
1 | // 注册用于动态刷新的 bean(如请求配置、URL 刷新器) |
我们看到,上面在注册 Feign Client 的Bean
定义的时候,会factoryBean.getObject()
用来注册,进入到这个方法中
会发现实际调用的是
FeignClientFactoryBean
中的this.getTarget();
方法,这个就是
Feign 注册Client的Bean工厂

进入这个方法, getTarget()
方法是该类中最核心的方法之一,用于构建并返回 Feign
接口的动态代理实例,也就是实际用于发起 RPC
调用的对象,该方法的主要作用是,根据 @FeignClient
注解的配置(已经被包装在其中,如
url
、name
、path
等),结合 Spring
容器中的组件(如负载均衡器、客户端配置等),创建 Feign 代理对象。
1 | <T> T getTarget() { |
我们可以看到,在处理 url 配置的时候,Feign
客户端有两种使用方式:通过服务名(结合负载均衡)调用 或
通过固定 URL 调用,getTarget()
方法会根据配置自动区分这两种场景。
!StringUtils.hasText(this.url) && !this.isUrlAvailableInConfig(this.contextId)
,无固定 URL(依赖负载均衡),会在@FeignClient
未指定url
属性,依赖服务名(如name = "user-service"
)通过注册中心(如 Eureka/Nacos)发现服务实例。- 核心逻辑
- 将服务名转换为基础 URL(如
user-service
→http://user-service
)。 - 调用
loadBalance()
方法,结合负载均衡器(如 Spring Cloud LoadBalancer)选择具体服务实例,最终创建代理对象。
- 将服务名转换为基础 URL(如
- 核心逻辑
- 有固定 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()
,生成包含InvocationHandler
和MethodHandler
的代理对象(即你之前关注的动态代理和方法处理器逻辑)。
因此,getTarget()
方法是 Spring 与 Feign
原生逻辑的「衔接点」,负责将 Spring 配置转化为 Feign
可识别的参数,最终生成可用的 RPC 代理对象。
也是在这里,集成了Spring
的负载均衡(LoadBalancer
)、配置管理(Environment
)、依赖注入(BeanFactory
)等功能,使
Feign 无缝融入 Spring 应用。
Feign如何构建请求
我们知道,Feign 不需要手写各种请求头的编写逻辑,那么 Feign 是的完整请求是如何构建的
在创建 Feign 接口的动态代理实例时,我们知道 Feign
会为每一个被注解标记的RPC接口创建一个调用处理器
InvocationHandler
,也会为接口的每一个RPC方法创建一个方法处理器MethodHandler
,并且将方法处理器缓存在调用处理器InvocationHandler
的dispatch
映射成员中。
从Targeter.target()
,,默认 Targeter
实现为
DefaultTargeter
,它会调用
Feign.Builder.target()
方法,最终由 Feign 原生的
ReflectiveFeign
生成代理对象,我们进入到
ReflectiveFeign
类中,最终会进入到这个方法

ReflectiveFeign.newInstance()
,这是
Feign 生成动态代理的入口,会创建 InvocationHandler
和
MethodHandler
,并关联到代理对象。
1 | /** |
该方法是 Feign 生成代理对象的核心逻辑,也是后续请求构建的基础。
我们也知道,在创建动态代理实例时,Feign也会通过RPC方法的注解为每一个RPC方法生成一个RequesTemplate请求模板实例,RequestTemplate中包含请求的所有信息,如请求URL、请求类型(如GET)、请求参数等。
我们知道,上面 newInstance
方法中,调用了Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = this.targetToHandlersByName.apply(target, requestContext);
,为代理对象创建methodToHandler
,而targetToHandlersByName
是ParseHandlersByName
的实例,调用
ParseHandlersByName
的 apply
方法。
1 | /** |
ParseHandlersByName
是 ReflectiveFeign
的内部类,负责为每个接口方法创建对应的
MethodHandler
(默认是
SynchronousMethodHandler
),它调用了
createMethodHandler(...)
创建
SynchronousMethodHandler
,并将 MethodMetadata
传入该处理器。
1 | private InvocationHandlerFactory.MethodHandler createMethodHandler(Target<?> target, MethodMetadata md, C requestContext) { |
看到 create 方法,我们一个劲的进入,发现
1 | public InvocationHandlerFactory.MethodHandler create(Target<?> target, MethodMetadata md, Object requestContext) { |
工厂类(默认 SynchronousMethodHandler.Factory
)在创建
SynchronousMethodHandler
时,会将
MethodMetadata
作为参数传入,并初始化
buildTemplateFromArgs
(用于后续构建
RequestTemplate
),而BuildTemplateByResolvingArgs
是专门用于根据 MethodMetadata
和方法参数构建
RequestTemplate
的工具类。
所以说,RequestTemplate
并非在代理创建时直接生成,而是在方法被调用时,根据
MethodMetadata
和实际参数动态构建。实现逻辑在
SynchronousMethodHandler
的 invoke
方法中,通过 BuildTemplateByResolvingArgs
完成。
当通过代理对象调用接口方法时,调用会被
FeignInvocationHandler
拦截,路由到对应的
SynchronousMethodHandler
:

1 | // 当代理对象调用接口方法时,此方法会被触发 |
1 | // 调用上述InvocationHandlerFactory中的MethodHandler中的invoke |
SynchronousMethodHandler
的 invoke
方法是请求构建的核心,会调用 buildTemplateFromArgs.create()
生成 RequestTemplate
:
1 | public Object invoke(Object[] argv) throws Throwable { |
到这里就结合
MethodMetadata
(静态注解信息)和方法实际参数(argv
),动态生成包含完整请求信息的
RequestTemplate
Feign如何发送请求
那么,请求是如何发送的
上面,我们知道在SynchronousMethodHandler
类中成功构建请求模板,到这里,RequestTemplate
包含请求的所有静态和动态信息(如
http://user-service/users/1
、GET
方法、请求头)。
而继续看SynchronousMethodHandler
类,上面 invoke
方法会调用一个 executeAndDecode
类,这个类会调用targetRequest
,这个方法会生成 HTTP
请求对象

1 | Request targetRequest(RequestTemplate template) { |
而且会将 RequestTemplate
转换为
Request
(包含最终 URL、请求方法、请求体字节流)。
之后,SynchronousMethodHandler.executeAndDecode()
调用底层
HTTP 客户端发送请求

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

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

而 Spring Cloud 通过 LoadBalancerFeignClient
包装默认客户端,实现服务发现与负载均衡,核心逻辑如下:
此时 SynchronousMethodHandler
中的 client
实际是 LoadBalancerFeignClient
,其 execute
方法会先通过负载均衡器获取服务实例,再调用底层客户端发送请求:

其中的 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 请求构建的完整链路如下
- 代理创建:通过
newInstance()
生成接口代理,绑定MethodHandler
与方法的映射。 - 方法调用:代理对象的方法被调用时,
FeignInvocationHandler
路由到对应的SynchronousMethodHandler
。 - 请求构建:
SynchronousMethodHandler
解析参数生成RequestTemplate
,应用拦截器后通过 HTTP 客户端发送请求。 - 响应处理:解码响应体并返回结果(或转换异常)。