当前位置: 首页 > news >正文

Spring Cloud OpenFeign 核心原理

Spring Cloud OpenFeign 是一个声明式的 Web 服务客户端,它通过使用注解来简化 HTTP API 的调用过程,使得编写 Web 服务客户端变得更加简单。OpenFeign 可以帮助我们快速地集成 RESTful 服务,并且可以方便地与 Spring Cloud 生态系统中的其他组件(如 Eureka、Ribbon、LoadBalancer 等)一起使用。

主要特性

• 声明式 REST 客户端:只需创建一个接口并用注解来配置它,就可以轻松实现对其他 HTTP 服务的绑定,而不需要手动编写大量的模板代码。
• 易于集成:能够很好地与其他 Spring Cloud 组件集成,比如服务发现( Eureka )、负载均衡( Ribbon / LoadBalancer )等。
• 灵活的配置:支持自定义编码器、解码器、错误处理器等,以满足不同的需求。
• 支持多种HTTP方法:支持 GET 、 POST 、 PUT 、 DELETE 、文件上传下载等功能。

简单使用示例

pom.xml 添加依赖:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在主类或者配置类上添加 @EnableFeignClients 注解来启用 Feign 客户端支持:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableFeignClients
public class FeignApplication {public static void main(String[] args) {SpringApplication.run(FeignApplication.class, args);}
}

定义 Feign 客户端接口:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;// 使用 @FeignClient 注解指定要调用的服务名称;若不依赖服务发现机制(如 Eureka),则需要配置 url    
@FeignClient(name = "greeting-service", url = "http://localhost:8090")
public interface GreetingServiceClient {@GetMapping("/api/greeting")String getGreeting();
}

使用 Feign 客户端:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class GreetingServiceImpl implements GreetingService {private final GreetingServiceClient greetingServiceClient;@Autowiredpublic GreetingServiceImpl(GreetingServiceClient greetingServiceClient) {this.greetingServiceClient = greetingServiceClient;}@Overridepublic String getGreeting() {// 直接使用 Feign 客户端return greetingServiceClient.getGreeting();}
}

核心原理

Spring Cloud OpenFeign 的核心实现是 声明式 REST 客户端 和 动态代理机制 。

核心组件
• @EnableFeignClients:这个注解用于启动 Feign 客户端的支持。在 Spring Boot 应用中,当你添加了这个注解后,Spring 会扫描指定包下的所有带有 @FeignClient 注解的接口,并为它们创建代理对象。
• @FeignClient:通过该注解定义一个 Feign 客户端,可以指定服务名(用于服务发现)、URL、编码器、解码器等属性。每个被标记的接口都会被增强生成 JDK 动态代理对象,实际请求会通过这些动态代理对象发送出去。
• Feign.Builder:Feign 的核心构建者类,它负责根据配置创建具体的 Feign 客户端实例。Spring Cloud 对默认的 Builder 进行了扩展,加入了负载均衡( Ribbon / LoadBalancer )、熔断器( Hystrix )等功能。
• LoadBalancerFeignClient:当与 Spring Cloud LoadBalancer 集成时,OpenFeign 使用的是一种特殊的服务请求客户端 —— LoadBalancerClient,它能够利用 Ribbon / Spring Cloud LoadBalancer 提供的负载均衡策略来选择服务实例进行调用。
• Decoder, Encoder, Logger, ErrorDecoder 等:这些是Feign的内部组件,分别用于处理响应的反序列化、请求的序列化、日志记录以及错误处理等功能。Spring Cloud 允许开发者自定义这些组件的行为。

工作流程
• 1、初始化: 在 Spring 容器启动期间,Spring Cloud 会扫描所有标注有@FeignClient的接口,为它们生成 JDK 动态代理对象,然后注入到 Spring 容器中。
• 2、创建Feign客户端: 利用 Feign.builder() 方法结合各种配置(如编码器、解码器、拦截器、接口注解配置等),构造出 Feign 动态代理客户端。如果整合了负载均衡器,则会使用 LoadBalancerClient 作为最终的客户端实现。
• 以 spring-cloud-starter-openfeign 包的 4.2.1 版本源码为例:

public class FeignClientFactoryBeanimplements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {/*** FeignClientFactoryBean 的 getObject() 方法是 Spring 容器用来获取由该工厂 Bean 创建的 Feign 客户端实例的方法。* getObject() 方法最终返回的是一个动态代理对象,这个对象实现了 @FeignClient 定义的接口,并能够将接口方法调用转换为 HTTP 请求*/@Overridepublic Object getObject() {return getTarget();}/*** 创建并返回一个 Feign 客户端实例*/@SuppressWarnings("unchecked")<T> T getTarget() {// 获取 FeignClientFactory 实例FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class): applicationContext.getBean(FeignClientFactory.class);// 使用 FeignClientFactory 构建 Feign.Builder 实例 (代码见后)Feign.Builder builder= feign(feignClientFactory);// 如果 URL 未提供且不在配置中可用,则尝试通过负载均衡选择实例if (!StringUtils.hasText(url) && !isUrlAvailableInConfig(contextId)) {if (LOG.isInfoEnabled()) {LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");}if (!name.startsWith("http://") && !name.startsWith("https://")) {url = "http://" + name;} else {url = name;}url += cleanPath();// 通过负载均衡创建客户端动态代理实例 (代码见后)return (T) loadBalance(builder, feignClientFactory, new HardCodedTarget<>(type, name, url));}// 否则使用固定 URL 创建客户端if (StringUtils.hasText(url) && !url.startsWith("http://") && !url.startsWith("https://")) {url = "http://" + url;}// 获取服务请求客户端:// 1、如果没有额外引入任何 HTTP 客户端库(如 Apache HttpClient 或 OkHttp),// 并且也没有启用负载均衡组件(Ribbon 或 Spring Cloud LoadBalancer),// 那么默认使用的 Client 是 Feign 自带的基于 HttpURLConnection 的实现。// 2、如果启用了负载均衡组件,则使用的 Client 默认是 FeignBlockingLoadBalancerClient(未开启失败重试时)Client client= getOptional(feignClientFactory, Client.class);if (client != null) {// 如果启用负载均衡组件(Spring Cloud LoadBalancer),但由于这里不需要负载均衡,// 所以通过 getDelegate() 获取到具体的 HTTP 请求客户端(比如 HttpURLConnection)即可if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}// 应用自定义构建器定制化applyBuildCustomizers(feignClientFactory, builder);// 获取 Targeter 实例,并使用它来创建目标客户端动态代理实例Targeter targeter= get(feignClientFactory, Targeter.class);return targeter.target(this, builder, feignClientFactory, resolveTarget(feignClientFactory, contextId, url));}/*** 创建并返回一个已配置好的 Feign.Builder 实例*/protected Feign.Builder feign(FeignClientFactory context) {FeignLoggerFactory loggerFactory= get(context, FeignLoggerFactory.class);Logger logger= loggerFactory.create(type);// @formatter:offFeign.Builder builder= get(context, Feign.Builder.class)// required values// 设置日志记录器,用于输出请求/响应详情.logger(logger)// 将 Java 对象编码为 HTTP 请求体(如 JSON、XML)。默认实现是 SpringEncoder,使用 Spring MVC 的 HttpMessageConverter。.encoder(get(context, Encoder.class))// Decoder:将 HTTP 响应体解码为 Java 对象。默认实现是 SpringDecoder,同样基于 HttpMessageConverter。.decoder(get(context, Decoder.class))// Contract:负责解析接口上的注解(如 @RequestMapping, @GetMapping 等)。默认是 SpringMvcContract,支持 Spring MVC 注解风格。.contract(get(context, Contract.class));// @formatter:on// 设置重试策略(Retryer)、错误处理器(ErrorDecoder)、请求拦截器(RequestInterceptor)、连接超时等配置项// 所有这些配置都支持用户自定义覆盖,默认值来自 Spring Boot 自动配置configureFeign(context, builder);return builder;}/*** 通过负载均衡创建客户端动态代理实例* @param builder 已配置好的 Feign.Builder 实例* @param context FeignClientFactory,用于从 Spring 容器中获取 Bean* @param target 一个封装了目标服务名称和 URL 的对象(通常是服务名,例如 http://service-name)*/protected <T> T loadBalance(Feign.Builder builder, FeignClientFactory context, HardCodedTarget<T> target) {// 获取服务请求客户端:// 如果启用了负载均衡组件,则使用的 Client 默认是 FeignBlockingLoadBalancerClient(未开启失败重试时)// FeignBlockingLoadBalancerClient 内会通过负载均衡获取一个服务实例,把服务名替换为真实IP和端口号,再发起 HTTP 请求。Client client = getOptional(context, Client.class);if (client != null) {// 设置 HTTP 请求客户端builder.client(client);// 应用额外定制化配置applyBuildCustomizers(context, builder);// 获取 TargeterTargeter targeter = get(context, Targeter.class);// 创建动态代理实例return targeter.target(this, builder, context, target);}throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");}}public class ReflectiveFeign<C> extends Feign {// FeignClientFactoryBean 中的 targeter.target(...) 最终都会调用到 ReflectiveFeign.newInstance(...) 方法创建动态代理对象@SuppressWarnings("unchecked")public <T> T newInstance(Target<T> target, C requestContext) {TargetSpecificationVerifier.verify(target);Map<Method, MethodHandler> methodToHandler =targetToHandlersByName.apply(target, requestContext);InvocationHandler handler= factory.create(target, methodToHandler);// 最终返回的是一个 JDK 动态代理对象T proxy=(T)Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler);for (MethodHandler methodHandler : methodToHandler.values()) {if (methodHandler instanceof DefaultMethodHandler) {((DefaultMethodHandler) methodHandler).bindTo(proxy);}}return proxy;}}
public class FeignBlockingLoadBalancerClient implements Client {/*** 真正执行 HTTP 请求的底层客户端(如 Apache HttpClient、OkHttp、JDK HttpURLConnection 等)* FeignBlockingLoadBalancerClient 是一个装饰器模式的应用,它将实际请求委托给这个 delegate 执行*/private final Client delegate;/*** 用于服务发现和实例选择的负载均衡客户端* Spring Cloud 2020 之前的旧版本是 RibbonLoadBalancerClient* Spring Cloud 2020 之后的新版本是 BlockingLoadBalancerClient*/private final LoadBalancerClient loadBalancerClient;// (省略其他)...// 执行 HTTP 请求public Response execute(Request request, Request.Options options) throws IOException {// 将请求的 URL 解析为 URIfinal URI originalUri = URI.create(request.url());// 提取主机名作为 serviceId(即服务名称),比如 "order-service"String serviceId = originalUri.getHost();Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);// 获取负载均衡策略使用的“hint”,通常是从请求头中提取的路由提示信息,比如可以基于请求头指定调用某个区域的服务实例String hint = getHint(serviceId);// 构建负载均衡请求上下文,request 里会包含请求体、头、方法等信息。DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(buildRequestData(request), hint));// 生命周期方法回调Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),RequestDataContext.class, ResponseData.class, ServiceInstance.class);supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));// 根据服务名称,选取服务实例// choose() 方法背后调用了 Ribbon 或 Spring Cloud LoadBalancer 的负载均衡策略算法(如轮询、随机、权重等)ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(instance);// 如果没有找到可用服务实例,构造一个 503 响应返回,同时通知生命周期处理器请求失败if (instance == null) {String message = "Load balancer does not contain an instance for the service " + serviceId;supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(CompletionContext.Status.DISCARD, lbRequest, lbResponse)));return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value()).body(message, StandardCharsets.UTF_8).build();}// 重构 URL:使用选中的 ServiceInstance 替换原始 URL 中的 host 部分。// 例如把 http://order-service/api/order/1 变成 http://192.168.1.10:8080/api/order/1String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();Request newRequest = buildRequest(request, reconstructedUrl, instance);// 通过底层的 HTTP 客户端(delegate)发送请求,并通知生命周期处理器执行回调方法 return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,supportedLifecycleProcessors);}
}

• 3、发起HTTP请求: 应用代码从 Spring 容器中获取到的 Feign 客户端,实际上是上面步骤 2 构造出来的 JDK 动态代理。当程序中调用 Feign 客户端的方法时,实际上是在调用由 JDK 动态生成的代理对象的方法,这个代理对象会将方法调用转换为 HTTP 请求,然后通过 HTTP 客户端发送出去。
• 4、响应处理: 收到响应后,相应的解码器会被用来解析响应内容,并将其转换为目标方法的返回值类型。

工作流程总结

在这里插入图片描述

Feign 与 Ribbon 整合的完整流程:
在这里插入图片描述
Feign 与 Spring Cloud LoadBalancer 整合的完整流程:
在这里插入图片描述

版本注意
Spring Cloud 从 2020.0.0 版本( Ilford 版本)开始,官方宣布不再对 Feign 进行维护和支持,并推荐使用 OpenFeign 作为替代方案。2020 版本后若要继续使用 Feign,依赖包需要从原来的 spring-cloud-starter-feign 替换为 spring-cloud-starter-openfeign 。

OpenFeign 是 Feign 的一个开源分支,将继续得到维护和支持,并且兼容 Spring Cloud生态系统。 OpenFeign 虽然核心上不直接包含与 Spring Cloud 组件(如 Eureka、Ribbon、Hystrix)的良好集成,但是可以通过配置来实现类似的集成效果,并且由于其灵活性,可以更容易地与其他工具和服务整合。

同时,Spring Cloud 从 2020.0.0 版本( Ilford 版本)开始也正式移除了对 Ribbon 的支持,在这个版本及之后的版本中,Ribbon 不再作为默认的客户端负载均衡器包含在 Spring Cloud 的发布版中,取而代之的是 Spring Cloud LoadBalancer 。

版本差异总结:

• Spring Cloud 2020.x 及之后:Feign(原生)已停止维护,默认使用 OpenFeign,而 OpenFeign 默认使用 Spring Cloud LoadBalancer。
• 旧版本(如 Spring Cloud Hoxton):Feign 和 OpenFeign 均默认使用 Ribbon 作为负载均衡组件。


文章转载自:

http://I5RTAgo8.cxryx.cn
http://UkbCLNG1.cxryx.cn
http://eQ5D5TrI.cxryx.cn
http://hckoBkP4.cxryx.cn
http://6Zx2JWJB.cxryx.cn
http://o9clJfe4.cxryx.cn
http://Fj8AcGiL.cxryx.cn
http://8eryLTYb.cxryx.cn
http://7mGgNbTh.cxryx.cn
http://Mkhjngrw.cxryx.cn
http://lMvd7Sah.cxryx.cn
http://E45hGY4b.cxryx.cn
http://Nn8h9VA3.cxryx.cn
http://04eVegiM.cxryx.cn
http://1WqWzJKs.cxryx.cn
http://i7ybP7fr.cxryx.cn
http://z8mwHYhr.cxryx.cn
http://BcEA5tK3.cxryx.cn
http://G5QByf8J.cxryx.cn
http://janUT5eF.cxryx.cn
http://05WOMKCg.cxryx.cn
http://bpdc0lML.cxryx.cn
http://SbnkrSaZ.cxryx.cn
http://VIrbBWYO.cxryx.cn
http://2qflEwGJ.cxryx.cn
http://IItbIk35.cxryx.cn
http://LLgmKbBi.cxryx.cn
http://SC2xOegR.cxryx.cn
http://kohkQPjr.cxryx.cn
http://66NcfqLq.cxryx.cn
http://www.dtcms.com/a/367284.html

相关文章:

  • 【华为培训笔记】OptiX OSN 9600 设备保护专题
  • 解决 ES 模块与 CommonJS 模块互操作性的关键开关esModuleInterop
  • 解密llama.cpp:Prompt Processing如何实现高效推理?
  • 抽象与接口——Java的“武器模板”与“装备词条”
  • 数组本身的深入解析
  • Linux Centos7搭建LDAP服务(解决设置密码生成密文添加到配置文件配置后输入密码验证报错)
  • 记录一下tab梯形圆角的开发解决方案
  • java面试中经常会问到的dubbo问题有哪些(基础版)
  • illustrator-04
  • 观察者模式-红绿灯案例
  • 【LLM】FastMCP v2 :让模型交互更智能
  • Linux下开源邮件系统Postfix+Extmail+Extman环境部署记录
  • 在Anaconda下安装GPU版本的Pytorch的超详细步骤
  • 追觅科技举办2025「敢梦敢为」发布会,发布超30款全场景重磅新品
  • 从“AI炼金术”到“研发加速器”:一个研发团队的趟坑与重生实录
  • B站 XMCVE Pwn入门课程学习笔记(9)
  • 【数学建模学习笔记】机器学习回归:XGBoost回归
  • 本地部署开源数据生成器项目实战指南
  • Agentic AI 架构全解析:到底什么是Agentic AI?它是如何工作的
  • AI助力软件UI概念设计:卓伊凡收到的客户设计图引发的思考
  • 零样本学习与少样本学习
  • QT6(事件与信号及事件过滤器)
  • JavaAI炫技赛:电商系统商品管理模块的创新设计与实践探索
  • 移动端WebView调试 iOS App网络抓包与请求分析工具对比
  • 给文件加密?企业文件加密软件有哪些?
  • 【C语言】第二课 位运算
  • 【正则表达式】 正则表达式匹配位置规则是怎么样的?
  • 【LeetCode数据结构】设计循环队列
  • Python 第三方自定义库开发与使用教程
  • Browser Use 浏览器自动化 Agent:让浏览器自动为你工作