【Spring Cloud 进阶】OpenFeign 底层原理解析

news2024/11/17 21:41:51

参考文章

  • 万字+33张图探秘OpenFeign核心架构原理 | 三友
  • SpringCloud OpenFeign源码详细解析
  • Java 代理机制

OpenFeign 是一个精彩的使用动态代理技术的典型案例,通过分析其底层实现原理,我们可以对动态代理技术有进一步的理解。

目录

    • 1. Feign 与 OpenFeign
      • 1.1 OpenFeign 的使用
      • 1.2 Feign 的原生使用
    • 2. Feign 生成代理实例的原理
      • 2.1 Feign 大致原理
      • 2.2 Feign 的核心组件一览
      • 2.3 动态代理生成原理
        • 2.3.1 Feign 的 Builder
        • 2.3.2 Feign Builder 的 target 函数
        • 2.3.3 build 出来的 Feign 实例是什么
        • 2.3.4 ReflectiveFeign 是如何创建出代理类的
        • 2.3.5 InvocationHandler 的 invoke 方法
        • 2.3.6 总结动态代理的生成逻辑 ⭐⭐⭐⭐⭐
    • 3. Feign 的一次 HTTP 调用执行的过程
    • 4. 总结

1. Feign 与 OpenFeign

Feign 由 Netflix 开源,Spring Cloud 对 Feign 进行了封装整合从而形成了 OpenFeign 项目。所以这篇文章之后的内容不再可以区分 Feign 和 OpenFeign。

在 Spring Cloud 项目中,以下代码可以引入 OpenFeign 依赖:

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

1.1 OpenFeign 的使用

在 Spring Cloud 中使用 OpenFeign 还是很简单的,可以参考 Feign 远程调用

1.2 Feign 的原生使用

OpenFeign 是 Spring Cloud 官方对 Feign 封装整合之后的用法,原生 Feign 的用法有些区别。

加入我们想通过 Feign 来调用 https://tenapi.cn/v2/yiyan 这个公开接口,我们可以这样做:

  • 首先声明一个 TenAPIClient 的 interface:
public interface TenAPIClient {

    @RequestLine("GET /v2/yiyan")
    String yiYan();
}
  • 使用:
public class OrderFeignDemo {

    public static void main(String[] args) {
        TenAPIClient tenAPIClient = Feign.builder()
                .target(TenAPIClient.class, "https://tenapi.cn");
        String s = tenAPIClient.yiYan();
        System.out.println(s);
    }
}

在上面例子中,我们可以看到,我们只需要声明一个 TenAPIClient 的 interface,以及 API 的调用函数声明,Feign 就可以通过动态代理技术生成一个实现了能够发起远程调用这个 API 的 client 实例。这就是 Feign 对动态代理技术的妙用。

2. Feign 生成代理实例的原理

这里的源码基于 Spring Boot 3.2.3,Spring Cloud 2023.0.0

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

2.1 Feign 大致原理

下图展示了 Feign 框架的原理,它根据 interface 使用动态代理生成代理类,我们使用代理类发起了 HTTP 请求并得到响应结果。
Feign 大致原理

2.2 Feign 的核心组件一览

在我们的 main 代码中的 Feign.builder() .target(TenAPIClient.class, "https://tenapi.cn"); 用来生成代理类,这里的 builder 中的成员就是 Feign 的各个核心组件了,我们来看一下:

Builder
在这里插入图片描述
简要介绍一下各个组件的作用:

  • Client:一个 HTTP Client 的接口,具体实现可以是 JDK 内置的 HttpURLConnection,也可以是 Apache HttpClient 或 OkHttp,Feign 使用这个 Client 来发送 HTTP 请求。
  • Contract:解析 interface 中方法的注解和参数,用于得知各个 API 的相关信息。这个组件在代码中通过解析各个方法的注解和参数(比如 @RequestLine("GET /v2/yiyan")),获得 List<MethodMetadata>,即各个 method 的 meta data,比如 URL、body、header 等等信息。
  • Retryer:用于实现重试的逻辑
  • RequestInterceptorResponseInterceptor:是一个拦截接口,通过这个接口,可以实现在 Http 请求发送之前或接收到响应之后对内容进行修改。
  • EncoderDecoder:用于序列化请求和反序列化响应的接口
  • InvocationHandlerFactory:用于创建 InvocationHandler,我们都知道,JDK 动态代理就是通过 InvocationHandler 来生成代理实例的,所以 InvocationHandler 的 invoke() 方法的逻辑就是动态代理走的核心逻辑。

2.3 动态代理生成原理

关于如何通过动态代理来获得 TenAPIClient 的实现类,其实就是分析 Feign.builder().target(TenAPIClient.class, "https://tenapi.cn"); 这一行代码在内部做了什么工作。

Feign 这个类是什么呢?在源码中有这样一行注释:

In implementation, Feign is a factory for generating targeted http apis.

所以,Feign 就是一个用来创建 TenAPIClient 实例的 factory。

2.3.1 Feign 的 Builder

Feign.builder() 会创建一个 Builder 类,用来创建 Feign,这个 Builder 类描述了 Feign 的各个关键组件,即 2.2 节介绍的那些,比如 Contract、Client、Retryer 等等,当我们需要 Feign 满足一些我们特殊需求时,我们可以通过 Builder 来替换个别关键组件,比如替换 HTTP Client 的实现、替换 Retryer 来更换重试策略等。在 Spring Cloud 整合 Feign 时,就通过这里来替换了一些关键组件,比如替换了重试策略等。

下面看一下 Feign.builder().target(TenAPIClient.class, "https://tenapi.cn"); 中 target 所做的事情:

target 实现
我们调用的是第一个 target 函数,传入了 TenAPIClient.class 和 baseURL,这样他就生成了一个 TenAPIClient 的代理实现类。

2.3.2 Feign Builder 的 target 函数

在第一个 target 函数中,他使用 apiType 和 url 初始化了一个 HardCodedTarget 类实例,HardCodedTarget 类实现了 Target 接口,这个接口是用来将一个 RequestTemplate 实例转换为一个真正的 Request 实例的。

第一个 target 函数调用了第二个 target 函数,第二个 target 函数就复杂了,它先 build() 创建了一个 Feign 实例,我们知道 Feign 就是一个 factory,这个 factory 创建出代理类,就如图中 target 函数的实现一样,build 出一个 Feign 实例后,就通过 newInstance() 创建出一个代理类。

所以现在来看看 build() 创建出的 Feign 实例是什么,以及 Feign 实例如何创建出代理类的。

2.3.3 build 出来的 Feign 实例是什么

build() 函数最终通过如下函数来构建出 Feign 实例:

build函数

这里有两个需要注意的点:

  • 我们生成的 Feign 实例是 ReflectiveFeign 的类实例(因为 Feign 只是一个抽象类)
  • 我们有了一个 SynchronousMethodHandler 的 factory,它用来生成 MethodHandler。MethodHandler 是用来实现代理类中各个方法调用的逻辑的,比如我们在 TenAPIClient 中声明的 yiYan() 方法的具体逻辑就是由一个 MethodHandler 来实现的。

既然我们知道了 Feign 的实现类就是 ReflectiveFeign,那我们就仔细看一下 ReflectiveFeign 的代码。

2.3.4 ReflectiveFeign 是如何创建出代理类的

这个问题的代码都在 ReflectiveFeign 的 newInstance() 方法中,我们来看一下:

newInstance

上图中被红色方框圈中的代码是核心代码,我们重点看这一部分。

其中,methodToHandler 是一个从 Method 到 MethodHandler 的 map,Method 也就是我们在 TenAPIClient 接口中定义的方法,MethodHandler 我们之前提到了,就是用来处理这个方法的具体逻辑,即包含真正执行远程调用的逻辑。targetToHandlersByName.apply(target, requestContext); 创建出这个 map 的原理大概就是,通过 Contract 解析出各个方法的 MethodMetaData,然后根据 Target、MethodMetaData 创建出实现了这个远程调用逻辑的 MethodHandler,从而构建出这个 map,这部分代码如下:

在这里插入图片描述
然后,我们接着看前面 newInstance 的核心代码,在得到 Method -> MethodHandler 的 map 后,接着创建出 InvocationHandler,学过 JDK 动态代理的都知道,InvocationHandler 是用来创建出最终的代理类的东西,这部分忘了的可以看一下 Java 代理机制 的 JDK 动态代理部分。

这里的 Proxy、InvocationHandler 都是 JDK 定义的,也是 JDK 实现的动态代理技术。所以 Feign 只需要按照 JDK 的要求去实现出自己的 InvocationHandler 实现,就能创建出一个实现了指定接口的动态代理类实例。

InvocationHandler 实现了代理的逻辑,当我们在 main 中写出 tenAPIClient.yiYan(); 时,其实就是调用了 InvocationHandler 实例的 invoke() 方法,这里的 invoke 方法就实现了向 API 发送 HTTP 请求并接收响应的逻辑。

在得到 InvocationHandler 的实例后,就通过 Proxy.newProxyInstance() 并传入 classLoader、所需要代理的类(在我们例子中就是 TenAPIClient)和 InvocationHandler,然后 JDK 动态代理就生成了一个代理类,也就是我们 main demo 中的 tenAPIClient 变量实例。

当我们调用 tenAPIClient 的 yiYan() 方法时,它其实就是调用了 InvocationHandler 的 invoke 方法,所以我们需要看一下这里 invoke 方法的实现是什么,它是如何实现了向 API 发送 HTTP 请求的。

2.3.5 InvocationHandler 的 invoke 方法

这里所说的 InvocationHandler 的实现类是 FeignInvocationHandler

FeignInvocationHandler

可以看到 invoke 的实现很简单,关键在于 return 后面这一行,就是根据你调用的函数是什么,找到相应的 MethodHandler,然后调用 MethodHandler 的 invoke 方法,执行 MethodHandler 中存放的远程调用的代码逻辑。比如 tenAPIClient.yiYan(); 这个调用就是调用的 FeignInvocationHandler 的 invoke 方法,invoke 方法知道你调用的方法名是 yiYan,然后找到这个 method handler,然后 method handler 执行其远程调用的逻辑,实现最终的远程调用。

2.3.6 总结动态代理的生成逻辑 ⭐⭐⭐⭐⭐

在这里插入图片描述
总结如下:我们编写出 TenAPIClient 的接口和接口方法,Feign 通过 Contract 解析这个接口和其中的方法,得到各方法的 MethodMetaData,然后由此创建出各方法的 MethodHandler,再利用这些 method handlers 实现出 InvocationHandler,使用这个 InvocationHandler 利用 JDK 动态代理技术创建出动态代理对象。

3. Feign 的一次 HTTP 调用执行的过程

前面介绍原理时,也介绍的差不多了。当我们调用接口的方法时,动态代理技术会交由 InvocationHandler 的 invoke 方法来处理,invoke 方法根据你调用的方法名找到相应的 method handler 并执行相应的远程调用逻辑。用图示来解释:

在这里插入图片描述
以上就是 Feign 一次 HTTP 调用的执行过程。

4. 总结

以上介绍了 Netflix 开源的 Feign 的实现原理,它精彩地运用了动态代理技术,实现了只需要我们写出接口方法,Feign 就生成了具体的远程调用逻辑,而我们只需要关注远程调用的 API 相关参数即可。

本篇文章未涉及 Spring Cloud 如何整合 Feign,关于整合 Feign 的部分在之后再深入研究。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1479270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实验笔记之——Ubuntu20.04配置nvidia以及cuda并测试3DGS与SIBR_viewers

之前博文测试3DGS的时候一直用服务器进行开发&#xff0c;没有用过笔记本&#xff0c;本博文记录下用笔记本ubuntu20.04配置过程&#xff5e; 学习笔记之——3D Gaussian Splatting源码解读_3dgs运行代码-CSDN博客文章浏览阅读3.2k次&#xff0c;点赞34次&#xff0c;收藏62次…

智能指针(C++)

目录 一、智能指针是什么 二、为什么需要智能指针 三、智能指针的使用和原理 3.1、RALL 3.2 智能指针的原理 3.3、智能指针的分类 3.3.1、auto_ptr 3.3.2、unique_ptr 3.3.3、shared_ptr 3.2.4、weak_ptr 一、智能指针是什么 在c中&#xff0c;动态内存的管理式通过一…

从 iOS 设备恢复数据的 20 个iOS 数据恢复工具

作为 iPhone、iPad 或 iPod 用户&#xff0c;您可能普遍担心自己可能会丢失存储在珍贵 iOS 设备中的所有宝贵数据。数据丢失的原因多种多样&#xff0c;这里列出了一些常见原因&#xff1a; 1. iOS 软件更新 2. 恢复出厂设置 3. 越狱 4. 误操作删除数据 5. iOS 设备崩溃 …

【论文精读】DINOv2

摘要 学习与特定任务无关的预训练表示已经成为自然语言处理的标准&#xff0c;这些表示不进行微调&#xff0c;即可在下游任务上明显优于特定任务模型的性能。其主要得益于使用无监督语言建模目标对大量原始文本进行预训练。 遵循NLP中的这种范式转变&#xff0c;以探索计算机视…

Linux学习-C语言-运算符

目录 算术运算符&#xff1a; - * /:不能除0 %:不能对浮点数操作 &#xff1a;自增与运算符 i&#xff1a;先用再加 i:先加再用 --&#xff1a;自减运算符 常量&#xff0c;表达式不可以&#xff0c;--&#xff0c;变量可以 赋值运算符 三目运算符 逗号表达式 size…

Linux系统加固:如何有效管理系统账号

Linux系统加固&#xff1a;如何有效管理系统账号 1.1 口令重复次数限制1.2 避免系统存在uid相同的账号1.3 空密码的帐户1.4 口令复杂度1.5 口令生存期1.6 登录失败次数锁定策略 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Linux系统中…

Mac OS 制作可引导安装器并重新安装系统

Mac 使用 U盘或移动固态硬盘制作可引导的安装器&#xff08;以 Monterey 为例&#xff09; 本教程参考 Apple 官网相关教程 创建可引导 Mac OS 安装器 重新安装 Mac OS 相关名词解释 磁盘分区会将其划分为多个单独的部分&#xff0c;称为分区。分区也称为容器&#xff0c;不同…

全面介绍HTML的语法!轻松写出网页

文章目录 heading(标题)paragraph(段落)link(超链接)imagemap(映射)table(表格)list(列表)layout(分块)form(表单)更多输入:datalistautocompleteautofocusmultiplenovalidatepatternplaceholderrequired head(首部)titlebaselinkstylemetascriptnoscript iframe HTML&#xff…

Linux 模拟实现shell【简单实现】

shell的模拟实现 我们知道shell是一个永不退出的程序&#xff0c;所以他应该是一个死循环&#xff0c;并且shell为了防止影响到自己&#xff0c;我们在命令行上输入的所有命令都是由shell的子进程来执行的&#xff0c;所以它应该要有创建子进程的相关函数&#xff0c;当然也会…

react 原理揭秘

1.目标 A. 能够知道setState()更新数据是异步的 B. 能够知道JSX语法的转化过程 C. 能够说出React组件的更新机制 D. 能够对组件进行性能优化 E. 能够说出虚拟DOM和Diff算法 2.目录 A. setState()的说明 B. JSX语法的转化过程 C. 组件更新机制 D. 组件性能优化 E. 虚拟DOM和D…

高效备考2025年AMC8数学竞赛:2000-2024年AMC8真题练一练

如何提高小学和初中数学成绩&#xff1f;小学和初中可以参加的数学竞赛有哪些&#xff1f;不妨了解一下AMC8美国数学竞赛&#xff0c;现在许多小学生和初中生都在参加这个比赛。如果孩子有兴趣&#xff0c;有余力的话可以系统研究AMC8的历年真题&#xff0c;即使不参加AMC8竞赛…

live555学习 - 环境准备

环境&#xff1a;Ubuntu 16.04.7 ffmpeg-6.1 1 代码下载 最新版本&#xff1a; http://www.live555.com/liveMedia/public/ 历史版本下载 https://download.videolan.org/pub/contrib/live555/ 选择版本live.2023.01.19.tar.gz ps&#xff1a;没有选择新版本是新版本在…

SuMa++代码阅读记录

文章目录 流程梳理1. 打开点云文件2. 播放点云数据3. SUMA部分的流程图说明3.1 SUMA核心流程分析&#xff0c;其中也包含部分SUMA3.2 preprocess部分3.3 updatePose部分3.4 updateMap部分 4. SUMA中有关语义模型rangenet的部分4.1 下面是解析模型引擎4.2 下面这块是从配置文件中…

洛谷P1044题解

复制Markdown 展开 题目背景 栈是计算机中经典的数据结构&#xff0c;简单的说&#xff0c;栈就是限制在一端进行插入删除操作的线性表。 栈有两种最重要的操作&#xff0c;即 pop&#xff08;从栈顶弹出一个元素&#xff09;和 push&#xff08;将一个元素进栈&#xff09…

stm32触发硬件错误位置定位

1.背景 1. 项目中&#xff0c;调试过程或者测试中都会出现程序跑飞问题&#xff0c;这个时候问题特别难查找。 2. 触发硬件错误往往是因为内存错误。这种问题特别难查找&#xff0c;尤其是产品到了测试阶段&#xff0c;而这个异常复现又比较难的情况下&#xff0c;简直头疼。…

CDH6.3.1离线安装

一、从官方文档整体认识CDH 官方文档地址如下&#xff1a; CDH Overview | 6.3.x | Cloudera Documentation CDH是Apache Hadoop和相关项目中最完整、测试最全面、最受欢迎的发行版。CDH提供Hadoop的核心元素、可扩展存储和分布式计算&#xff0c;以及基于Web的用户界面和重…

虚拟游戏理财【华为OD机试-JAVAPythonC++JS】

题目描述 题目描述&#xff1a; 在一款虚拟游戏中生活&#xff0c;你必须进行投资以增强在虚拟游戏中的资产以免被淘汰出局。现有一家Bank&#xff0c;它提供有若干理财产品m&#xff0c;风险及投资回报不同&#xff0c;你有N&#xff08;元&#xff09;进行投资&#xff0c;能…

Python:练习:编写一个程序,录入一个美元数量(int),然后显示出增加%5税率后的相应金额。

案例&#xff1a; 编写一个程序&#xff0c;录入一个美元数量&#xff08;int&#xff09;&#xff0c;然后显示出增加%5税率后的相应金额。格式如下所示&#xff1a; Enter an amount:100 With tax added:$105.0 思考&#xff1a; 1、录入一个美元数量&#xff0c;录入&am…

qt学习:实战 记事本 + 快捷键 + 鼠标滚轮 + 打开读取写入关闭文件

目录 功能 步骤 配置ui界面 添加图片资源 添加头文件和定义成员数据和成员函数 在构造函数里初始化 增加当前字体大小函数 减小当前字体大小函数 在用户按下 Ctrl 键的同时滚动鼠标滚轮时&#xff0c;执行放大或缩小操作 多选框变化后发出信号绑定槽函数来改变编码 …

flutter学习(一) 安装以及配置环境

首先需要下载flutter&#xff0c;然后解压 然后配置环境变量&#xff0c;配置到bin目录就行 配置完之后cmd运行flutter doctor 你就会发现&#xff0c;都是错 此时脑海里响起&#xff0c;卧槽&#xff0c;怎么回事&#xff0c;咋办 别着急&#xff0c;我教你。。。 问题 这…