一、spring-cloud-feign 中的代理模式
Feign 是 Spring cloud 微服务架构组件之一,Feign 功能主要是远程 API 调用。
Feign 相关使用案例代码如下:
![[设计模式]-[结构型]-代理模式-应用案例 - 图1](/uploads/projects/it-learn@java-base/73ea76a011f725b750cc11bc073656dd.webp)
上述 Feign APi 调用程序代码中,
存在两个服务,一个是 consumer 一个是 provider 。
consumer通过Feign 发起远程调用,调用 provider 中的 API 接口 /hello/name
通过上述代码能够知道,consumer 发起远程 API 调用,代码直接是调用的 Feign Client 接口类 HelloClient。
总结 Feign 案例代码信息能够得到三点:
1、远程服务名称 provider
2、远程 API 接口 /hello/name 3、代码定义的API调用,只是一个接口类
通过上述信息能够知道,Feign 需要通过这些信息发起远程 API 调用, Feign 需要做的就是将 provider 置换成 IP + port的具体请求地址,然后结合 API 接口 /hello/name 拼接请求 url ,发起 HTTP 请求。
而完成这些功能的 Feign Client 也就是 HelloClient 只是一个接口,该接口只是做了方法的定义,而真正实现功能的是 Feign Client 的代理类。
以上述代码案例,Feign Client 接口,也就是 HelloClient 在进行实例化加入到 Spring IOC 容器时,会构建一个代理对象,该代理对象的逻辑图如下:
![[设计模式]-[结构型]-代理模式-应用案例 - 图2](/uploads/projects/it-learn@java-base/da16157c58cfc694273c2ca869699aa2.png)
Feign Clicent 最终实例化的对象,有两部分内容需要关注:
HardCodedTargetReflectiveFeign.FeignInvocationHandler
分别来看一下 **HardCodedTarget** 对象 和 **ReflectiveFeign.FeignInvocationHandler**
HardCodedTarget
代码如下
![[设计模式]-[结构型]-代理模式-应用案例 - 图3](/uploads/projects/it-learn@java-base/5de9d2162896086da880d88985b68b5d.webp)
该对象存储了远程调用的相关信息
- type :
存储的就是 Feign Client 类,也就是HelloClient - name :
存储的就是远程服务名,也就是provider - url :
存储的就是远程API调用,也就是http://provider
ReflectiveFeign.FeignInvocationHandler
![[设计模式]-[结构型]-代理模式-应用案例 - 图4](/uploads/projects/it-learn@java-base/e0911064b9b9850886681c2de8729a18.webp)
如上述代码,该对象是典型的的 JDK 动态代理类,真正进行 请求发送的代码在该代理类中完成。
具体 Http 请求操作,在
`dispatch.get(method).invoke(args)中完成。 其中 `dispatch.get(method)` 获取到的 对象为 `SynchronousMethodHandler` ` 这里面包含了负载均衡,链路追踪等,自行进行扩展。
总结
在 SPring Cloud 中使用 Feign 组件进行远程API 调用,只需要定义 Feign Client 接口类就可以进行 API 调用,如下图:
![[设计模式]-[结构型]-代理模式-应用案例 - 图5](/uploads/projects/it-learn@java-base/a2d6162990a4804de0959e80cbd4ab62.webp)
其原理是因为,代码底层通过 JDK 动态代理,为 Feign Client 做了代理,所有操作都在代理中进行。
二、Mybatis 中 Mapper 接口动态代理
Mybatis 面试题中,最喜欢问的一个问题,XXXMapper.java 接口,只是一个接口,为什么直接调用就能够发起数据库请求?
Mybatis 中最重要的类就是 **Configuration**,构建 Configuration 的类为 XMLConfigBuilder ,在进行 Configuration 构建的时候,相关的 **XXXMapper.java** 接口同样会被读取存储在 Configuration 中的 **MapperRegister**。
MapperRegister 注册 Mapper.java 接口的相关代码如下:
![[设计模式]-[结构型]-代理模式-应用案例 - 图6](/uploads/projects/it-learn@java-base/76e28aa7ac13b912d70a619903c064dc.webp)
在进行 XXXMapper.java 信息缓存的时候,Mybatis 真实缓存的是 **MapperProxyFactory**类,代理工厂类。
来看看代理工厂类具体内容
![[设计模式]-[结构型]-代理模式-应用案例 - 图7](/uploads/projects/it-learn@java-base/b5e5ce86de1332925c371bac3cc11ce1.webp)
如上述代码,直接根据 XXXMapper.java 接口进行代理类的创建,对应的代理类为 **MapperProxy** ,来看看代理类 MapperProxy ,具体查看其 invoke 方法,相关代码如下:
![[设计模式]-[结构型]-代理模式-应用案例 - 图8](/uploads/projects/it-learn@java-base/70fdc4e12ecb9bbf2ea9eaa422a0549b.webp)
如上代码,**MapperProxy** 是一个 JDK 动态代理类,而在 **invoke** 方法中会调用 **MapperMethod#execute** 进行 SQL 语句的拼接调用。
总结
Mybatis 中针对每一个 XXXMapper.java 接口进行代理,相关逻辑图如下:
![[设计模式]-[结构型]-代理模式-应用案例 - 图9](/uploads/projects/it-learn@java-base/12221b5251511966079dc6f6bf11c774.webp)
回到问题:为什么直接调用 XXXMapper.java 接口就能够进行 SQL 查询,因为在代码运行期间,XXXMapper.java 会被代理类 MapperProxy 代理,所有的操作都由 代理类来完成。
三、项目应用中的代理模式
Springframework AOP 本身实现就是 代理模式,项目中使用 springframework 框架时并用到了 AOP,就相当于使用了 代理。
【公众号】花好夜猿
