SpringCloud原理-OpenFeign篇(三、FeignClient的动态代理原理)

文章目录

  • 前言
  • 正文
    • 一、前戏,FeignClientFactoryBean入口方法的分析
      • 1.1 从BeanFactory入手
      • 1.2 AbstractBeanFactory#doGetBean(...)中对FactoryBean的处理
      • 1.3 结论 FactoryBean#getObject()
    • 二、FeignClientFactoryBean实现的getObject()
      • 2.1 FeignClientFactoryBean#getTarget()
      • 2.2 获取`Targeter`实例
      • 2.3 ReflectiveFeign#newInstance(...)
      • 2.4 生成代理对象
    • 三、动态代理原理全流程梳理
  • 附录
    • 附1:本系列文章链接

前言

本篇是SpringCloud原理系列的 OpenFeign 模块的第三篇。

主要内容是接第二篇,在将FeignClientFactoryBean 的bean描述器注册到容器中后,我们的容器在初始化时,使用了饥饿模式,直接创建Bean。本文就围绕FeignClientFactoryBean来分析动态代码的应用,以及它本身的初始化过程。

使用java 17,spring cloud 4.0.4,springboot 3.1.4

正文

一、前戏,FeignClientFactoryBean入口方法的分析

首先看看它的类图:
在这里插入图片描述
实现了众多接口,我们先记得它实现了 FactoryBean接口。
后文分析时有用到。

1.1 从BeanFactory入手

我们都知道,从Spring 容器中获取一个bean,都会用到BeanFactory的实现类。

在众多继承关系中,AbstractBeanFactory 的存在,帮助实现了很多特殊逻辑。也包括对FactoryBean实现类的处理。
在这里插入图片描述
在这个抽象Bean工厂中,实现了getBean的方法。

public Object getBean(String name) throws BeansException {return this.doGetBean(name, (Class)null, (Object[])null, false);
}public <T> T getBean(String name, Class<T> requiredType) throws BeansException {return this.doGetBean(name, requiredType, (Object[])null, false);
}public Object getBean(String name, Object... args) throws BeansException {return this.doGetBean(name, (Class)null, args, false);
}public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args) throws BeansException {return this.doGetBean(name, requiredType, args, false);
}

可以看到,都是直接调用了一个 doGetBean 方法。

1.2 AbstractBeanFactory#doGetBean(…)中对FactoryBean的处理

这个doGetBean方法太长了,我这里不做粘贴,只挑重点的说。

在这个方法中,获取Bean的时候,有调用 getObjectForBeanInstance(...)方法。而该方法中就对FactoryBean 做了处理。

处理逻辑如下:在这里插入图片描述
做了判断,如果当前实例是factoryBean的类型,就调用了getCachedObjectForFactoryBeangetObjectFromFactoryBean。这里有优化,从缓存中获取不到时,会从工厂中获取,也就是去创建实例。在这里插入图片描述

1.3 结论 FactoryBean#getObject()

这里的关键方法,doGetObjectFromFactoryBean 就使用了FactoryBean接口。

    private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {Object object;try {object = factory.getObject();} catch (FactoryBeanNotInitializedException var5) {throw new BeanCurrentlyInCreationException(beanName, var5.toString());} catch (Throwable var6) {throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", var6);}if (object == null) {if (this.isSingletonCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject");}object = new NullBean();}return object;}

可以看到调用了FactoryBean#getObject(),并返回了对应的实例。而这也就是本篇的开始。

二、FeignClientFactoryBean实现的getObject()

从类的定义上,FeignClientFactoryBean 实现了FactoryBean。
所以,在从容器中获取该类型实例时,也就会调用到getObject()

实现如下:

@Override
public Object getObject() {return getTarget();
}

你没看错,它就一行代码,但是就这一行代码,其复杂程度却丝毫不小。

2.1 FeignClientFactoryBean#getTarget()

源码如下:

<T> T getTarget() {// 从容器获取FeignClientFactory实例FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class): applicationContext.getBean(FeignClientFactory.class);// 获取feign的建造器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")) {url = "http://" + name;}else {url = name;}url += cleanPath();return (T) loadBalance(builder, feignClientFactory, new HardCodedTarget<>(type, name, url));}if (StringUtils.hasText(url) && !url.startsWith("http")) {url = "http://" + url;}// 处理urlString url = this.url + cleanPath();// 通过工厂获取client实例Client client = getOptional(feignClientFactory, Client.class);// 生成clientif (client != null) {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);// 返回解析,使用动态代理绑定MethodHandler,最终返回代理对象return targeter.target(this, builder, feignClientFactory, resolveTarget(feignClientFactory, contextId, url));}

以上的核心步骤其实就是以下几步:

  1. 从容器中获取FeignClientFactory 实例;
  2. 依据FeignClientFactory 实例生成Feign.Builder实例;
  3. 拼接有效的url
  4. 获取client
  5. 处理自定义构建参数
  6. 获取Targeter实例
  7. 动态代理获取代理对象

本文主要关注的是第6、7步。

2.2 获取Targeter实例

首先,Targeter有两个实现类:DefaultTargeterFeignCircuitBreakerTargeter
默认是从容器中直接获取到DefaultTargeter。如果使用了断路器,则会获取到 FeignCircuitBreakerTargeter

默认实现如下:

	@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,Target.HardCodedTarget<T> target) {return feign.target(target);}

其中 Feign.Builder#target(...) 如下:

@Override
public <T> T target(Target<T> target) {return this.build().newInstance(target);
}

而这里的build()方法,则会生成一个ReflectiveFeign 实例。
在这里插入图片描述
使用创建的ReflectiveFeign 实例调用newInstance(...)

2.3 ReflectiveFeign#newInstance(…)

在这里插入图片描述
分行去分析本部分代码。

Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = this.targetToHandlersByName.apply(target, requestContext);

映射Method 和 新生成MethodHandler为Map:

public Map<Method, InvocationHandlerFactory.MethodHandler> apply(Target target, C requestContext) {Map<Method, InvocationHandlerFactory.MethodHandler> result = new LinkedHashMap();// contract解析校验元数据List<MethodMetadata> metadataList = this.contract.parseAndValidateMetadata(target.type());Iterator var5 = metadataList.iterator();while(var5.hasNext()) {MethodMetadata md = (MethodMetadata)var5.next();// 获取方法,其实就是接口中的方法封装成了多个MethodMetadataMethod method = md.method();if (method.getDeclaringClass() != Object.class) {// 创建MethodHandlerInvocationHandlerFactory.MethodHandler handler = this.createMethodHandler(target, md, requestContext);// map 映射method 和 handlerresult.put(method, handler);}}// 处理默认方法Method[] var10 = target.type().getMethods();int var11 = var10.length;for(int var12 = 0; var12 < var11; ++var12) {Method method = var10[var12];if (Util.isDefault(method)) {InvocationHandlerFactory.MethodHandler handler = new DefaultMethodHandler(method);result.put(method, handler);}}return result;}// 创建MethodHandler,默认是SynchronousMethodHandler类型的private InvocationHandlerFactory.MethodHandler createMethodHandler(Target<?> target, MethodMetadata md, C requestContext) {return md.isIgnored() ? (args) -> {throw new IllegalStateException(md.configKey() + " is not a method handled by feign");} : this.factory.create(target, md, requestContext);}

Method和MethodHandler的映射结果如下:
可以看到Method是自己定义的FeignClient接口中的一个方法。Handler是SynchronousMethodHandler的实例。
在这里插入图片描述
随后将这个Map简单校验后,透传到InvocationHandlerdispatch属性:
在这里插入图片描述

2.4 生成代理对象

T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), 
new Class[]{target.type()}, handler);

以接口维度,生成对应接口的代理对象,并绑定 2.3 小节中生成的handler。
在这里插入图片描述

三、动态代理原理全流程梳理

以生成以下接口的代理为例。

@FeignClient(name = "helloFeignClient", url = "http://localhost:10080")
public interface HelloFeignClient {@PostMapping("/hello/post")HelloResponse postHello(@RequestBody HelloRequest helloRequest);
}

在这里插入图片描述

附录

附1:本系列文章链接

SpringCloud原理-OpenFeign篇(一、Hello OpenFeign项目示例)
SpringCloud原理-OpenFeign篇(二、OpenFeign包扫描和FeignClient的注册原理)
SpringCloud原理-OpenFeign篇(三、FeignClient的动态代理原理)

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

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

相关文章

oepnpnp - 自己出图做开口扳手

文章目录 oepnpnp - 自己出图做开口扳手概述笔记做好的一套扳手实拍美图工程图END oepnpnp - 自己出图做开口扳手 概述 我的openpnp设备顶部相机安装支架, 由于结构限制, 螺柱的安装位置和机械挂壁的距离太近了. 导致拧紧(手工或者工具)很困难. 也不能重新做相机支架, 因为将…

构建和应用卡尔曼滤波器 (KF)--扩展卡尔曼滤波器 (EKF)

作为一名数据科学家&#xff0c;我们偶尔会遇到需要对趋势进行建模以预测未来值的情况。虽然人们倾向于关注基于统计或机器学习的算法&#xff0c;但我在这里提出一个不同的选择&#xff1a;卡尔曼滤波器&#xff08;KF&#xff09;。 1960 年代初期&#xff0c;Rudolf E. Kal…

腾讯云CVM标准型S5性能如何?CPU采用什么型号?

腾讯云服务器CVM标准型S5实例具有稳定的计算性能&#xff0c;CVM 2核2G S5活动优惠价格280.8元一年自带1M带宽&#xff0c;15个月313.2元、2核4G配置748.2元15个月&#xff0c;CPU内存配置还可以选择4核8G、8核16G等配置&#xff0c;公网带宽可选1M、3M、5M或10M&#xff0c;腾…

传输层——UDP协议

文章目录 一.传输层1.再谈端口号2.端口号范围划分3.认识知名端口号4.两个问题5.netstat与iostat6.pidof 二.UDP协议1.UDP协议格式2.UDP协议的特点3.面向数据报4.UDP的缓冲区5.UDP使用注意事项6.基于UDP的应用层协议 一.传输层 在学习HTTP等应用层协议时&#xff0c;为了便于理…

【Python】可再生能源发电与电动汽车的协同调度策略研究

1 主要内容 之前发布了《可再生能源发电与电动汽车的协同调度策略研究》matlab版本程序&#xff0c;本次发布的为Python版本&#xff0c;采用gurobi作为求解器&#xff0c;有需要的可以下载对照学习研究。 首先详细介绍了优化调度模型的求解方案&#xff0c;分别采用二次规划…

初识linux(1)

文章目录 什么是linux什么是操作系统&#xff1f;开源 怎么装linux的环境基础指令lspwdcdtouchmkdirrmdir与rmmancpmv 什么是linux linux是一款开源操作系统 什么是操作系统&#xff1f; 操作系统&#xff1a;一种对计算机所有计算机软硬件进行控制和管理的系统软件 开源 开源&…

centos7卸载mongodb数据重新安装时无法安装的问题

如果卸载不干净直接用 sudo find / -name mongo 查询所有关于mongo的文件&#xff0c;然后一个个去删除。 当然最好的办法还是去看日志信息。 直接去查看日志信息 sudo cat /var/log/mongodb/mongod.log 根据提示信息说这个没有权限操作 直接删除即可&#xff0c;都是之前…

全球首款容器计算产品重磅发布,激活上云用云新范式

云布道师 10 月 31 日&#xff0c;杭州云栖大会上&#xff0c;阿里云云原生应用平台负责人丁宇宣布&#xff0c;阿里云容器计算服务 ACS 正式发布&#xff01;ACS 将大幅降低企业和开发者用云门槛&#xff0c;真正将 Serverless 理念大规模落地。 容器计算服务 ACS&#xff0c…

ssm+vue的OA办公系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的OA办公系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 项目介绍&a…

Windows网络「SSL错误问题」及解决方案

文章目录 问题方案 问题 当我们使用了神秘力量加持网络后&#xff0c;可能会和国内的镜像源网站的之间发生冲突&#xff0c;典型的有 Python 从网络中安装包&#xff0c;如执行 pip install pingouin 时&#xff0c;受网络影响导致无法完成安装的情况&#xff1a; pip config…

idea中的sout、psvm快捷键输入,不要太好用了

目录 一、操作环境 二、psvm、sout 操作介绍 2.1 psvm&#xff0c;快捷生成main方法 2.2 sout&#xff0c;快捷生成打印方法 三、探索 psvm、sout 底层逻辑 一、操作环境 语言&#xff1a;Java 工具&#xff1a; 二、psvm、sout 操作介绍 2.1 psvm&#xff0c;快捷生成m…

笔尖笔帽检测3:Android实现笔尖笔帽检测算法(含源码 可是实时检测)

目录 1. 前言 2.笔尖笔帽检测方法 (1)Top-Down(自上而下)方法 (2)Bottom-Up(自下而上)方法&#xff1a; 3.笔尖笔帽关键点检测模型训练 4.笔尖笔帽关键点检测模型Android部署 &#xff08;1&#xff09; 将Pytorch模型转换ONNX模型 &#xff08;2&#xff09; 将ONNX模…

FPGA模块——IIC协议(读写PCF8591)

FPGA模块——IIC协议&#xff08;读取PCF8591&#xff09; PCF8591/AT8591芯片对iic协议的使用 PCF8591/AT8591芯片 低功耗8位CMOS数据采集设备&#xff0c;4路模拟输入&#xff0c;1路模拟输出&#xff0c;分时多路复用&#xff0c;读取数据用串型iic总线接口&#xff0c;最大…

Redis:Java客户端

前言 "在当今大数据和高并发的应用场景下&#xff0c;对于数据缓存和高效访问的需求日益增长。而Redis作为一款高性能的内存数据库&#xff0c;以其快速的读写能力和丰富的数据结构成为众多应用的首选。与此同时&#xff0c;Java作为广泛应用于企业级开发的编程语言&…

新安装win11,搜索框无法输入的问题

正确的做法是如下: 1首先进入win11系统&#xff0c;在搜索框中输入“ 控制面板 ”将其打开2在控制面板中找到“时间和语言“ 标题 再选择“ 语言和区域”, 标题 在显示的语言上面&#xff0c;点击省略号&#xff0c;进入语言选项 标题 在键盘处&#xff0c;删除不需要的输入法…

想面试前端工程师,必须掌握哪些知识和技能?【云驻共创】

在当今的数字化时代&#xff0c;前端工程师扮演着至关重要的角色。他们负责设计和开发用户界面&#xff0c;使得用户能够与应用程序或网站进行互动。为了找到最出色的前端工程师&#xff0c;你需要了解哪些技能和知识是必备的&#xff0c;同时也要掌握一些面试技巧和常见的面试…

Java 开源重试类 guava-retrying 使用案例

使用背景 需要重复尝试执行某些动作&#xff0c;guava-retrying 提供了成型的重试框架 依赖 <dependency><groupId>com.github.rholder</groupId><artifactId>guava-retrying</artifactId><version>${retrying.version}</version>…

90天,广告商单43张,小红书AI庭院风视频制作详解教程

今天给大家分享一个目前在小红书很火的AI绘画商单号案例。 首先给大家看看案例视频形态 这类视频内容非常简单&#xff0c;主要展示农家庭院的别致景色。通过AI绘画工具生成图片&#xff0c;再利用剪辑工具将画面增加动态元素&#xff0c;让整个视频逼真鲜活&#xff0c;加上…

PHP中isset() empty() is_null()的区别

在PHP中&#xff0c;isset()、empty()和is_null()是用于检查变量状态的三个不同的函数。它们分别用于检查变量是否已设置、是否为空以及是否为null。在本文中&#xff0c;我们将详细解释这三个函数的用法、区别和适当的使用场景。 isset(): isset()函数用于检查一个变量是否已…

《洛谷深入浅出基础篇》 P5250 木材仓库————集合应用实例

上链接&#xff1a; P5250 【深基17.例5】木材仓库 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P5250上题干&#xff1a; 题目描述 博艾市有一个木材仓库&#xff0c;里面可以存储各种长度的木材&#xff0c;但是保证没有两个木材的长度是…