【SpringCloud合集-03】Spring Cloud OpenFeign 声明式服务调用学习笔记
目录
一、核心定位与演进背景
1.1 什么是 OpenFeign
1.2 Feign 与 OpenFeign 的区别与演进关系
1. 演进背景
2. 核心差异对比
3. 代码写法直观差异
1.3 与 RestTemplate 的对比
二、Spring Cloud 整合实战
2.1 基础整合三步法
第一步:引入依赖
第二步:开启 Feign 功能
第三步:定义 Feign 接口
2.2 常用配置项
全局配置(所有服务生效)
2.3 常见参数传递场景与踩坑
1. GET 请求传递对象参数
2. 多参数 GET 请求
3. 请求头传递
三、核心执行流程与源码解析
3.1 完整调用链路图
3.2 @EnableFeignClients 扫描源码解析
3.3 动态代理生成源码解析
3.4 方法调用核心处理器源码
3.5 与 LoadBalancer 负载均衡整合原理
四、高级特性与扩展实战
4.1 日志级别配置
4.2 自定义请求拦截器
4.3 自定义异常解码器
OpenFeign 下的配置方式
4.4 切换 HTTP 客户端
五、实战踩坑与最佳实践
5.1 踩坑
5.2 生产最佳实践
六、面试速记总结
基于 Spring Cloud 2021.0.x + OpenFeign 3.1.x 版本学习
一、核心定位与演进背景
1.1 什么是 OpenFeign
OpenFeign 是 Spring Cloud 生态下的声明式 HTTP 客户端,它让远程服务调用变得和调用本地接口一样简单:开发者只需要定义接口、加上注解,就能自动生成 HTTP 请求逻辑,无需手动拼接 URL、封装参数、处理返回值。
它的前身是 Netflix Feign,Netflix 停止维护后,Spring 社区在其基础上进行了二次封装与持续迭代,正式命名为 Spring Cloud OpenFeign,成为 Spring Cloud 官方标准的服务间调用组件。
核心价值:
- 声明式编程:写接口 + 注解即可完成远程调用,代码简洁优雅
- 无缝整合 Spring MVC:支持
@RequestMapping、@RequestParam、@PathVariable等常用注解,学习成本极低 - 内置负载均衡:原生整合 Spring Cloud LoadBalancer,自动实现服务发现与负载调用
- 高度可扩展:支持自定义拦截器、编码器、解码器、异常处理器、重试策略
- 适配多种调用端:默认使用 JDK URLConnection,可切换为 OkHttp、Apache HttpClient 提升性能
术语说明:本笔记所有内容默认针对 Spring Cloud OpenFeign。日常开发中口语化的 “Feign 调用”,在 Spring Cloud 技术栈里默认指代的就是 OpenFeign,属于简称;严格技术层面两者是继承演进关系,并非完全等同。
1.2 Feign 与 OpenFeign 的区别与演进关系
很多资料里会混用两个名称,两者是前后演进的父子关系,核心定位相同,但生态适配、注解体系、维护主体差异很大。
1. 演进背景
- Netflix Feign(原生 Feign):最早由 Netflix 公司开源的轻量声明式 HTTP 客户端,自带一套独立注解体系,仅实现基础的 HTTP 请求封装,不依赖 Spring 生态,后期 Netflix 官方停止了该组件的迭代维护。
- Spring Cloud OpenFeign:Spring 社区在原生 Feign 内核基础上做二次封装与扩展,正式纳入 Spring Cloud 官方生态,全面适配 Spring MVC 注解,深度整合服务发现、负载均衡、熔断降级等微服务能力,是目前 Spring Cloud 技术栈的标准服务调用组件。
2. 核心差异对比
| 对比维度 | Netflix Feign(原生) | Spring Cloud OpenFeign |
|---|---|---|
| 维护主体 | Netflix 公司,已停止迭代 | Spring 官方社区,持续同步更新 |
| 注解体系 | 自有注解:@RequestLine、@Param、@Headers | 兼容 Spring MVC 注解:@GetMapping、@RequestParam、@PathVariable等 |
| Spring 生态适配 | 无原生支持,需手动整合进 Spring 项目 | 深度整合,原生支持 Spring 容器、环境配置、Bean 注入 |
| 微服务能力 | 无服务发现、负载均衡能力,只能写死地址调用 | 原生整合 Nacos/Eureka + LoadBalancer,支持直接用服务名调用 |
| 扩展性 | 仅基础编解码、拦截器扩展 | 丰富的配置化扩展点:全局 / 单服务配置、异常解码器、重试器、熔断适配 |
| 适用场景 | 非 Spring 项目的轻量 HTTP 调用 | Spring Cloud 微服务架构下的服务间标准调用 |
3. 代码写法直观差异
原生 Feign 写法(独立于 Spring)
// 使用 Feign 自有注解定义接口 public interface UserApi { @RequestLine("GET /user/{id}") User getUserById(@Param("id") Long id); } // 手动构建代理对象,写死目标地址 UserApi userApi = Feign.builder() .decoder(new JacksonDecoder()) .target(UserApi.class, "http://localhost:8081");OpenFeign 写法(Spring 生态)
// 使用 Spring MVC 注解,通过服务名定位目标 @FeignClient("user-service") public interface UserFeignClient { @GetMapping("/user/{id}") User getUserById(@PathVariable("id") Long userId); } // 直接注入使用,由 Spring 容器统一管理生命周期 @Autowired private UserFeignClient userFeignClient;1.3 与 RestTemplate 的对比
| 对比维度 | OpenFeign | RestTemplate |
|---|---|---|
| 编程风格 | 声明式接口,类似 MyBatis Mapper | 命令式调用,手动拼接参数 URL |
| 代码量 | 少,仅需定义接口 | 多,每个调用都要写请求逻辑 |
| 可读性 | 高,接口定义即文档 | 一般,业务代码与调用代码耦合 |
| 扩展性 | 强,统一配置拦截器、编解码 | 弱,需手动封装工具类 |
| 适用场景 | 服务间大量接口调用,追求代码规范 | 简单少量调用,灵活度要求高 |
面试考点:OpenFeign 本质是对 HTTP 调用的封装,底层依然是 HTTP 协议,不是 RPC 框架;它解决的是「调用代码繁琐」的问题,底层通信方式和 RestTemplate 没有本质区别。
二、Spring Cloud 整合实战
2.1 基础整合三步法
第一步:引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>版本说明:Spring Cloud 2020.0 之后的版本,OpenFeign 默认依赖 Spring Cloud LoadBalancer,不再内置 Ribbon。
第二步:开启 Feign 功能
在启动类添加@EnableFeignClients注解:
@SpringBootApplication @EnableFeignClients // 开启Feign客户端扫描 public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }第三步:定义 Feign 接口
/** * 用户服务远程调用接口 * value = 目标服务名,对应注册中心的服务名 */ @FeignClient("user-service") public interface UserFeignClient { /** * 路径参数调用:根据ID查询用户 * 注解使用方式和Controller完全一致 */ @GetMapping("/user/{id}") User getUserById(@PathVariable("id") Long userId); /** * 查询参数调用:多条件查询 * @RequestParam 必须指定value属性,否则会报错 */ @GetMapping("/user/list") List<User> listUserByName(@RequestParam("name") String name, @RequestParam("age") Integer age); /** * 请求体调用:新增用户 * @RequestBody 标注的对象会序列化为JSON放入请求体 */ @PostMapping("/user/add") Boolean addUser(@RequestBody User user); }业务层直接注入使用,和调用本地 Service 完全一致:
@Service public class OrderService { @Autowired private UserFeignClient userFeignClient; public Order getOrderDetail(Long orderId) { Order order = orderMapper.getById(orderId); // 远程调用用户服务,像调用本地方法一样 User user = userFeignClient.getUserById(order.getUserId()); order.setUser(user); return order; } }2.2 常用配置项
全局配置(所有服务生效)
feign: client: config: default: # default代表全局配置 connectTimeout: 2000 # 连接超时时间,单位毫秒,默认10s readTimeout: 5000 # 读取响应超时时间,单位毫秒,默认60s loggerLevel: full # 日志级别,默认NONE retryer: feign.DefaultRetryer # 重试策略,默认不重试 requestInterceptors: # 全局请求拦截器 - com.example.config.TokenInterceptor httpclient: enabled: true # 开启Apache HttpClient替代默认URLConnection,性能更好 max-connections: 200 # 最大连接数 max-connections-per-route: 50 # 单路由最大连接数指定服务单独配置
feign: client: config: user-service: # 写服务名,仅对该服务生效 connectTimeout: 3000 readTimeout: 8000优先级:单个服务配置 > 全局配置;同类配置会覆盖,不同类配置会合并。
2.3 常见参数传递场景与踩坑
1. GET 请求传递对象参数
坑点:Feign 默认不支持 GET 请求直接传对象,会自动把 GET 转成 POST,导致 405 错误。解决方案:使用@SpringQueryMap注解,自动把对象属性转为查询参数拼接到 URL 后。
@GetMapping("/user/query") User queryUser(@SpringQueryMap UserQuery query);2. 多参数 GET 请求
@RequestParam必须显式指定value属性,不能省略,否则启动报错。这是 Feign 和 Spring MVC 的区别,Spring MVC 可以省略参数名,Feign 必须明确指定。
| 维度 | Spring MVC (Controller) | Spring Cloud OpenFeign (接口) |
|---|---|---|
| 定义类型 | 实现类(Class),编译后包含方法参数表 | 接口(Interface),编译后参数名默认被擦除为arg0,arg1 |
| 参数名来源 | 从编译后的.class文件中获取(支持-parameters或 IDE 保留) | 接口无法可靠获取参数原名(容易被擦除) |
| Feign 的契约 | (不适用) | Feign 的SpringMvcContract在解析接口方法时,强制要求从注解中获取参数名。如果不指定value,它找不到绑定的名字,为了杜绝运行时userId变成null或arg0导致 URL 拼接错误,Feign 直接选择抛出异常阻止启动,把问题暴露在开发阶段。 |
3. 请求头传递
可以通过@RequestHeader注解在接口方法上指定单个请求头,也可以通过拦截器统一添加。
@GetMapping("/user/info") User getUserInfo(@RequestHeader("token") String token);三、核心执行流程与源码解析
3.1 完整调用链路图
3.2 @EnableFeignClients 扫描源码解析
Feign 的起点是@EnableFeignClients注解,它通过@Import导入了注册器,完成 Feign 接口的扫描与 Bean 注册。
// 注解定义 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) // 核心:导入Bean定义注册器 public @interface EnableFeignClients { String[] value() default {}; String[] basePackages() default {}; // 扫描包路径 Class<?>[] defaultConfiguration() default {}; }核心注册器FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,在 Spring 容器启动时执行扫描:
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 1. 注册默认配置 registerDefaultConfiguration(metadata, registry); // 2. 扫描所有加了@FeignClient的接口 registerFeignClients(metadata, registry); }扫描逻辑:
- 读取
@EnableFeignClients指定的扫描包,没指定则用启动类所在包 - 扫描包下所有接口,筛选出加了
@FeignClient注解的接口 - 为每个 Feign 接口生成一个
FeignClientFactoryBean工厂 Bean 定义,注册到容器 - 容器实例化时,通过工厂 Bean 生成接口的动态代理对象
设计思想:和 MyBatis 的 MapperScanner 思路完全一致,都是「扫描接口 → 工厂 Bean 生成代理 → 注入容器」,这是声明式框架的通用设计模式。
3.3 动态代理生成源码解析
每个 Feign 接口对应一个FeignClientFactoryBean,它实现了FactoryBean接口,getObject()方法返回的就是动态代理对象。
// FeignClientFactoryBean核心方法 @Override public Object getObject() { return getTarget(); } <T> T getTarget() { // 1. 从容器中获取Feign上下文工厂 FeignContext context = applicationContext.getBean(FeignContext.class); // 2. 构建Feign.Builder Feign.Builder builder = feign(context); // 3. 拼接目标服务URL String url = resolveUrl(); // 4. 生成JDK动态代理对象 return (T) builder.target(targetType, url); }最终通过ReflectiveFeign生成 JDK 动态代理,每个方法对应一个SynchronousMethodHandler方法处理器。调用接口方法时,实际是调用这个处理器的invoke方法。
3.4 方法调用核心处理器源码
SynchronousMethodHandler是每次方法调用的核心执行器,完整封装了一次 HTTP 请求的全流程。
@Override public Object invoke(Object[] argv) throws Throwable { // 1. 解析方法上的注解、参数,构建请求模板 RequestTemplate template = buildTemplateFromArgs.create(argv); // 2. 执行所有请求拦截器 for (RequestInterceptor interceptor : requestInterceptors) { interceptor.apply(template); } // 3. 执行请求,包含重试逻辑 return executeAndDecode(template); } Object executeAndDecode(RequestTemplate template) throws Throwable { // 构建最终请求 Request request = targetRequest(template); Response response; try { // 4. 调用客户端执行HTTP请求 // 如果整合了LoadBalancer,这里会先做负载均衡选实例 response = client.execute(request, options); } catch (IOException e) { // 重试判断:符合重试条件则重新发起请求 if (retryer.continueOrPropagate(e)) { return executeAndDecode(template); } throw e; } // 5. 解码器解析响应体,转为Java对象返回 return decoder.decode(response, metadata.returnType()); }3.5 与 LoadBalancer 负载均衡整合原理
Feign 本身不做负载均衡,它通过FeignBlockingLoadBalancerClient这个装饰器,把负载均衡能力织入到 HTTP 调用流程中。
/* * 源代码来自: spring-cloud-openfeign-core * 包路径: org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient */ package org.springframework.cloud.openfeign.loadbalancer; // ... 省略 import 语句 ... /** * 一个支持客户端负载均衡的 {@link feign.Client} 实现。 * 它本身不发送HTTP请求,而是一个"装饰器",核心职责是: * 1. 从请求URL中解析出目标服务的名称 (Service ID)。 * 2. 利用 Spring Cloud LoadBalancer 根据服务名选择一个合适的服务实例。 * 3. 用选中的实例的 IP 和端口,替换掉原请求 URL 中的服务名。 * 4. 将修改后的请求,委托(delegate)给真正的 HTTP 客户端(如 Apache HttpClient)去执行。 * * @author Olga Maciaszek-Sharma * @since 3.0.0 */ public class FeignBlockingLoadBalancerClient implements Client { // 被装饰的、真正执行HTTP请求的客户端 private final Client delegate; // Spring Cloud 负载均衡客户端,负责服务实例的选择 private final BlockingLoadBalancerClient loadBalancerClient; // 负载均衡客户端工厂,用于创建针对特定服务的负载均衡器 private final LoadBalancerClientFactory loadBalancerClientFactory; // ... 构造方法等代码 ... @Override public Response execute(Request request, Request.Options options) throws IOException { // 1. 从请求的 URL 中解析出 URI URI originalUri = URI.create(request.url()); // 2. 获取服务名:从 URI 的 Host 部分获取,例如 http://userservice/user/1 中的 "userservice" String serviceName = originalUri.getHost(); // 3. 断言服务名不能为空,否则无法进行负载均衡 Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); // 4. 从当前请求和上下文中,构建一个负载均衡请求 (LoadBalancerRequest) // 这个请求内部封装了"如何使用选中的服务实例来构建最终HTTP请求"的逻辑 LoadBalancerRequest<Response> loadBalancerRequest = new BuildLoadBalancerRequest(request, options); // 5. 执行负载均衡的核心逻辑: // - 根据服务名 serviceName,从注册中心获取可用服务实例列表。 // - 根据配置的负载均衡策略(如轮询、随机等),从中选择一个实例。 // - 调用 loadBalancerRequest 内部逻辑,用选中的实例构建新的 Request。 // - 委托 delegate 客户端执行这个新 Request,并返回响应。 return loadBalancerClient.execute(serviceName, loadBalancerRequest); } /** * 内部类,用于构建一个带负载均衡能力的请求。 * 它实现了 LoadBalancerRequest 接口,其核心逻辑在 apply 方法中。 */ private class BuildLoadBalancerRequest implements LoadBalancerRequest<Response> { private final Request request; private final Request.Options options; private BuildLoadBalancerRequest(Request request, Request.Options options) { this.request = request; this.options = options; } @Override public Response apply(ServiceInstance serviceInstance) throws Exception { // 1. 从选中的服务实例中,构建出具体的访问地址 (http://IP:PORT) URI reconstructedUri = LoadBalancerUriTools.reconstructURI(serviceInstance, URI.create(request.url())); // 2. 用这个真实的 IP:PORT 地址,创建一个新的 HTTP 请求 Request newRequest = buildRequest(request, reconstructedUri); // 3. 将这个新请求,交给被装饰的 delegate 客户端去真正执行 return FeignBlockingLoadBalancerClient.this.delegate.execute(newRequest, options); } private Request buildRequest(Request request, URI reconstructedUri) { // 将原请求的 URL 替换为构建好的真实地址 return Request.create(request.httpMethod(), reconstructedUri.toString(), request.headers(), request.body(), request.charset()); } } // ... 其他辅助方法,如 getDelegate() 等 ... }它是怎么“织入”的?
在 Spring Cloud 2021.x 版本中,虽然类名变为了 FeignBlockingLoadBalancerClient(因为 Ribbon 替换为了 Spring Cloud LoadBalancer),但背后的“装饰器”逻辑完全一致。
我们来看一个 Feign 请求从发起到执行的完整内部流程:
发起调用:
userClient.getUser(1L);Feign 构建请求:Feign 根据
@FeignClient("userservice"),构建出一个原始请求对象,此时目标地址是http://userservice/user/1(这里是服务名,不是具体的 IP)。执行拦截(装饰器生效):Feign 在真正发 HTTP 请求之前,会先去找注册的Client组件。因为我们开启了负载均衡,Spring Cloud 注入的不是 Feign 默认的
Client.Default,而是被包装过的LoadBalancerFeignClient(装饰器)。替换 URL(核心织入):
FeignBlockingLoadBalancerClient拦截到这个请求。
它提取出 URL 中的
userservice,去注册中心(如 Nacos)拉取userservice的所有可用实例列表[192.168.1.10:8080, 192.168.1.11:8080]。它调用负载均衡算法(如轮询),选中其中一个 IP,比如
192.168.1.11:8080。它将原始 URL重写为
http://192.168.1.11:8080/user/1。
真正发送请求:装饰器把修改后的请求,交给底层真正的 HTTP Client(如 OkHttp 或 Apache HttpClient)去执行。
设计解析:
- 采用典型的装饰器模式,在不修改原有 HTTP 客户端代码的前提下,增加了负载均衡能力。
- 服务名替换发生在请求执行前,上层业务完全无感知,实现了解耦。
- 这也是为什么 Feign 接口里直接写服务名就能调用的底层原因。
四、高级特性与扩展实战
4.1 日志级别配置
Feign 提供 4 种日志级别,用于调试和排查问题,生产环境建议关闭。
| 级别 | 说明 |
|---|---|
| NONE | 默认值,不输出任何日志,性能最好 |
| BASIC | 仅输出请求方法、URL、响应状态码、执行时间 |
| HEADERS | 在 BASIC 基础上,额外输出请求头和响应头 |
| FULL | 输出完整的请求体、响应体、头信息,调试最详细,性能最差 |
配置方式:
feign: client: config: default: loggerLevel: full # 同时需要开启Feign接口包的debug日志级别 logging: level: com.example.feign: debug4.2 自定义请求拦截器
最常用的扩展点,用于统一添加请求头、传递用户身份 Token、链路追踪 ID 等。
/** * 全局Token传递拦截器 * 所有Feign请求都会自动带上当前请求的token */ @Component public class TokenRelayInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { // 从当前请求上下文中获取token HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String token = request.getHeader("Authorization"); if (StringUtils.hasText(token)) { // 添加到Feign请求头中 template.header("Authorization", token); } } }常见坑点:异步线程、定时任务中调用 Feign 时,RequestContextHolder 拿不到请求,会报空指针,需要手动传递上下文。
4.3 自定义异常解码器
Feign 默认调用失败(非 200 状态码)时会直接抛出FeignException,不便于业务处理。可以自定义异常解码器,统一解析业务异常。
/** * Feign 业务异常解码器 * 底层实现原生Feign的ErrorDecoder接口,在OpenFeign体系中自动生效 */ public class BusinessExceptionDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { try { // 解析响应体中的业务错误信息 String body = Util.toString(response.body().asReader(StandardCharsets.UTF_8)); JSONObject errorJson = JSON.parseObject(body); String message = errorJson.getString("message"); int code = errorJson.getIntValue("code"); // 转换为项目自定义业务异常 return new BusinessException(code, message); } catch (IOException e) { return new FeignException(response.status(), "调用失败", null, null, null); } } }OpenFeign 下的配置方式
全局生效(所有 Feign 接口都使用):将解码器注册为 Spring Bean 即可
@Configuration public class FeignGlobalConfig { @Bean public ErrorDecoder businessErrorDecoder() { return new BusinessExceptionDecoder(); } }指定服务单独生效:放到对应服务的专属配置类中
// 仅对user-service服务生效 @FeignClient(value = "user-service", configuration = BusinessExceptionDecoder.class) public interface UserFeignClient { // 接口方法... }4.4 切换 HTTP 客户端
Feign 默认使用 JDK 自带的HttpURLConnection,没有连接池,性能一般。生产环境推荐切换为 Apache HttpClient 或 OkHttp。
切换为 Apache HttpClient:
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>feign: httpclient: enabled: true max-connections: 200 max-connections-per-route: 50 time-to-live: 900s五、实战踩坑与最佳实践
5.1 踩坑
GET 请求自动转 POST原因:GET 方法直接传对象参数,Feign 默认会把对象放入请求体,GET 请求没有请求体,底层自动转为 POST。 解决:单个参数用
@RequestParam,对象参数加@SpringQueryMap注解。@RequestParam 省略 value 启动报错原因:Feign 不像 Spring MVC 能自动推断参数名,必须显式指定
@RequestParam("name")的 value 属性。 解决:所有参数注解都显式写 value 值,养成规范。服务名下划线导致调用失败原因:LoadBalancer 解析主机名时,下划线不符合 URL 主机名规范,导致解析失败。 解决:服务命名统一使用中划线
-,不要用下划线_。超时时间过长导致雪崩原因:默认连接超时 10 秒、读取超时 60 秒,下游服务卡死时,大量请求会阻塞,耗尽线程资源。 解决:根据业务场景合理设置超时时间,一般 1~5 秒,配合熔断降级保护。
异步场景下请求头丢失原因:子线程调用 Feign 时,
RequestContextHolder拿不到主线程的请求,导致 token 等上下文丢失。 解决:手动传递请求属性到子线程,或者使用线程池装饰器统一透传。重试导致数据重复原因:开启 Feign 重试后,下游服务已经执行成功但响应超时,Feign 会重新发起请求,导致写操作重复执行。 解决:写接口保证幂等性;非幂等接口关闭重试;只对 GET 请求开启重试。
5.2 生产最佳实践
- 接口粒度:一个服务对应一个 Feign 接口,按业务模块拆分,不要一个接口写几十个方法。
- 超时配置:核心服务单独配置超时时间,全局配置设保守值,避免单点故障拖垮整个链路。
- 日志控制:生产环境关闭 FULL 级别日志,避免打印大量日志影响性能;排查问题时临时开启。
- 连接池:统一使用 Apache HttpClient 或 OkHttp,开启连接池复用,提升高并发下的性能。
- 统一异常处理:自定义错误解码器,统一封装业务异常,避免调用方处理原生 FeignException。
- 上下文透传:通过拦截器统一传递用户身份、链路追踪 ID、灰度标识等公共信息。
六、面试速记总结
- 核心本质:OpenFeign 是声明式 HTTP 客户端,基于 JDK 动态代理生成调用逻辑,底层依然是 HTTP 通信,替代手动封装的 RestTemplate。
- 启动流程:
@EnableFeignClients导入注册器,扫描@FeignClient接口,通过工厂 Bean 生成动态代理对象注入 Spring 容器。 - 调用流程:方法调用 → 动态代理拦截 → 构建请求模板 → 执行拦截器 → 负载均衡选实例 → HTTP 客户端发起请求 → 解码返回结果。
负载均衡整合:通过 FeignBlockingLoadBalancerClient装饰器模式,在请求执行前替换服务名为实际 IP,实现无缝整合。
- 常见坑:GET 传对象变 POST、参数注解必须写 value、服务名不能带下划线、重试要考虑幂等性。
- 设计模式:工厂模式(FeignClientFactoryBean)、装饰器模式(负载均衡客户端)、动态代理模式(核心调用逻辑)、拦截器模式(请求扩展)。