参考文章
- 万字+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 请求并得到响应结果。
2.2 Feign 的核心组件一览
在我们的 main 代码中的 Feign.builder() .target(TenAPIClient.class, "https://tenapi.cn");
用来生成代理类,这里的 builder 中的成员就是 Feign 的各个核心组件了,我们来看一下:
简要介绍一下各个组件的作用:
- 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:用于实现重试的逻辑
- RequestInterceptor 和 ResponseInterceptor:是一个拦截接口,通过这个接口,可以实现在 Http 请求发送之前或接收到响应之后对内容进行修改。
- Encoder 和 Decoder:用于序列化请求和反序列化响应的接口
- 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 函数,传入了 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 实例:
这里有两个需要注意的点:
- 我们生成的 Feign 实例是 ReflectiveFeign 的类实例(因为
Feign
只是一个抽象类) - 我们有了一个 SynchronousMethodHandler 的 factory,它用来生成 MethodHandler。MethodHandler 是用来实现代理类中各个方法调用的逻辑的,比如我们在 TenAPIClient 中声明的
yiYan()
方法的具体逻辑就是由一个 MethodHandler 来实现的。
既然我们知道了 Feign 的实现类就是 ReflectiveFeign,那我们就仔细看一下 ReflectiveFeign 的代码。
2.3.4 ReflectiveFeign 是如何创建出代理类的
这个问题的代码都在 ReflectiveFeign 的 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:
可以看到 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 的部分在之后再深入研究。