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

参考文章

  • 万字+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.hqwc.cn/news/499718.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙真有前景吗?是真是假?

直到“纯血鸿蒙”发布&#xff0c;才看清华为真正的布局&#xff0c;这一招实在是高明&#xff01; “纯血鸿蒙”发布之前&#xff0c;国内大批人唱衰华为&#xff0c;唱衰鸿蒙系统的生态&#xff0c;认为大概率会走诺基亚和微软的老路&#xff0c;没想到“纯血鸿蒙”一经推出…

Docker基础篇(五) dockerfile之基础内容

Dockerfile基础内容 1、每条保留字指令都必须大写字母且后面要跟随至少一个参数 –保留字&#xff1a;– FROM、 MAINTAINER 作者、维护者、 RUN、 EXPOSE 暴露、曝光 WORKDIR ENV ADD COPY VOLUME CMD ENTRYPOINT 进入点&#xff0c;入口 ONBUILD 2、指令按照从上到下&#x…

Machine Vision Technology:Lecture2 Linear filtering

Machine Vision Technology&#xff1a;Lecture2 Linear filtering Types of ImagesImage denoising图像去噪Defining convolution卷积的定义Key properties卷积的关键属性卷积的其它属性Annoying details卷积练习Sharpening锐化Gaussian KernelNoise噪声 分类Gaussian noise高…

Java图书管理系统---命令行

项目列表 Book包 Book类内包含book的基本属性 BookList类初始化图书列表并且提供图书的属性方法 User包 Administrator类 common类 operator包 功能接口 新增图书功能 借阅图书功能 删除图书功能 显示图书功能 查找图书功能 归还图书功能 结束释放资源功能 运行…

matlab实现不同窗滤波器示例

1 汉明窗低通滤波器 &#xff1a; 在Matlab中使用汉明窗设计低通滤波器可以通过fir1函数实现。汉明窗通常用于设计滤波器&#xff0c;可以提供更突出的频率特性。 下面是一个示例代码&#xff0c;演示如何在Matlab中使用汉明窗设计低通滤波器&#xff1a; % 定义滤波器参数 fs …

DOM 获取父子节点

DOM 是以树状结构排列的&#xff0c;所以父子关系是相对的&#xff0c;当li为我们的目标节点的时候&#xff0c;ul为其父节点&#xff0c;其他li为它的兄弟节点&#xff0c;li里面包含的标签为子节点&#xff0c;以此类推。 那我们如何找父节点&#xff1f; 元素.parentNode&am…

ROS2----运行helloworld、集成开发环境的搭建

前言&#xff1a;ROS2已经出来了&#xff0c;ROS1会被逐渐淘汰&#xff0c;大家尽量不要学ROS1了&#xff01;&#xff01; 文章目录 一、运行helloworld1.创建工作空间2.创建功能包3.源文件和配置文件4.编译与运行5.源码编写下的编译与运行6.运行优化 二、集成开发环境的搭建…

Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析

3. SpringBoot原理 SpringBoot使我们能够集中精力地去关注业务功能的开发&#xff0c;而不用过多地关注框架本身的配置使用。而我们前面所讲解的都是面向应用层面的技术&#xff0c;接下来我们开始学习SpringBoot的原理&#xff0c;这部分内容偏向于底层的原理分析。 在剖析Sp…

嵌入式中14 个超级牛的免费开源小工具

Homebrew for macOS 地址&#xff1a;https://brew.sh Mac 上非常好用的包管理工具&#xff0c;很多常见的安装都可以通过 brew install app 或者 brew cask install app 直接安装&#xff0c;类似 apt-get 。 Oh My Zsh 地址&#xff1a;https://github.com/robbyrussell…

基于springboot实现线上阅读系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现线上阅读系统演示 摘要 随着社会发展速度的愈来愈快&#xff0c;以及社会压力变化的越来越快速&#xff0c;致使很多人采取各种不同的方法进行解压。大多数人的稀释压力的方法&#xff0c;是捧一本书籍&#xff0c;心情地让自己沉浸在情节里面&#xff0c;以…

【2022 J1】乘方

本主又双叒叕来更新了&#xff0c;一圈三连不用说了吧&#x1f601; 本题是J组第二轮的题&#xff1a; 题目描述 小文同学刚刚接触了信息学竞赛&#xff0c;有一天她遇到了这样一个题&#xff1a;给定正整数 a 和 b&#xff0c;求 &#xfffd;&#xfffd;ab 的值是多少。 …

【iOS ARKit】协作 Session 实例

协作 Session 使用注意事项 协作 Session 是在 ARWorldMap 基础上发展起来的技术&#xff0c;ARWorldMap 包含了一系列的地标、ARAnchor 及在观察这些地标和 ARAnchor 时摄像机的视场&#xff08;View&#xff09;。如果用户在某一个位置新创建了一个 ARAnchor&#xff0c;这时…

DataGrip2023配置连接Mssqlserver、Mysql、Oracle若干问题解决方案

1、Mssqlserver连接 本人连的是Sql2008&#xff0c;默认添加时&#xff0c;地址、端口、实例、账号、密码后&#xff0c;测试连接出现错误。 Use SSL&#xff1a;不要勾选 VM option&#xff1a;填写&#xff0c;"-Djdk.tls.disabledAlgorithmsSSLv3, RC4, DES, MD5withR…

人工智能如何改变我们与世界的沟通的方式?

腾讯研究院 作为一名设计师、艺术家、科技研究人员,人工智能如何改变我们与世界的沟通的方式&#xff1f; 1.《音外话》 它是一款语音至视频生成人工智能的系统&#xff0c;它把我们的话语转化为充满情感的视觉影像。这个系统不仅可以捕捉语言的字面意义&#xff0c;还能理解…

小红书关键词爬虫

标题 1 统计要收集的关键词&#xff0c;制作一个文件夹2 爬取每一页的内容3 爬取标题和内容4 如果内容可以被查看&#xff0c;爬取评论内容5 将结果进行汇总&#xff0c;并且每个帖子保存为一个json文件&#xff0c;具体内容6 总结 1 统计要收集的关键词&#xff0c;制作一个文…

ts的重载

官网示例 TypeScript: Documentation - Template Literal Types 这里大概理解是 T 继承了Number|sting 加上&#xff1f;条件判断就是 T继承Number|sting 部分为true 没有继承部分为false&#xff0c; 就是输入string, 为true, 输入 null 则为false, type Exclude<T, U&…

八股文打卡day24——数据库(1)

面试题&#xff1a;左连接和右连接的区别&#xff1f; 我的回答&#xff1a; 左连接的SQL语句是&#xff1a;左表 left join 右表 on 连接条件&#xff0c;表示以左表为基础&#xff0c;将左表的的所有记录与右表进行连接。即使右表中没有与左表匹配的记录&#xff0c;左连接…

类和对象(2)——距离C++又近了一步

目录 一、构造函数 1.1声明和定义构造函数 1.2成员名和参数名 1.3构造函数的使用 1.4初始化列表 二、析构函数 2.1析构函数的概念 2.2析构函数的性质 三、拷贝构造函数 四、赋值运算符重载 4.1运算符重载 4.2赋值运算符重载 一、构造函数 我们知道&#xff0c;C中…

网络编程作业day2

1.将TPC和UDP通信模型各敲两遍 &#xff08;1&#xff09;TPC通信模型&#xff1a; 服务器代码&#xff1a; #include <myhead.h> #define SERVER_IP "192.168.125.136" #define SERVER_PORT 1314 int main(int argc, const char *argv[]) {//1、创建用于监…

Rocky Linux 运维工具 ls

一、ls 的简介 ​​ls​ 用于列出当前目录下的文件和目录&#xff0c;以及它们的属性信息。通过 ​ls​命令可以查看文件名、文件大小、创建时间等信息&#xff0c;并方便用户浏览和管理文件。 二、ls 的参数说明 序号参数描述1-a显示所有文件&#xff0c;包括以 ​.​开头的…