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.rhkb.cn/news/200473.html

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

相关文章

hadoop 配置历史服务器 开启历史服务器查看 hadoop (十)

1. 配置了三台服务器&#xff0c;hadoop22, hadoop23, hadoop24 2. hadoop文件路径: /opt/module/hadoop-3.3.4 3. hadoop22机器配置历史服务器的配置文件&#xff1a; 文件路径&#xff1a;/opt/module/hadoop-3.3.4/etc/hadoop 文件名称&#xff1a;mapred-size.xml 新增历…

用 HLS 实现 UART

用 HLS 实现 UART 介绍 UART 是一种旧的串行通信机制&#xff0c;但仍在很多平台中使用。它在 HDL 语言中的实现并不棘手&#xff0c;可以被视为本科生的作业。在这里&#xff0c;我将通过这个例子来展示在 HLS 中实现它是多么容易和有趣。 因此&#xff0c;从概念上讲&#xf…

【日常总结】Swagger-ui 导入 showdoc (优雅升级Swagger 2 升至 3.0)

一、场景 环境&#xff1a; 二、存在问题 三、解决方案 四、实战 - Swagger 2 升至 3.0 &#xff08;Open API 3.0&#xff09; Stage 1&#xff1a;引入Maven依赖 Stage 2&#xff1a;Swagger 配置类 Stage 3&#xff1a;访问 Swagger 3.0 Stage 4&#xff1a;获取 js…

Windows系统搭建VisualSVN服务并结合内网穿透实现公网访问

目录 前言 1. VisualSVN安装与配置 2. VisualSVN Server管理界面配置 3. 安装cpolar内网穿透 3.1 注册账号 3.2 下载cpolar客户端 3.3 登录cpolar web ui管理界面 3.4 创建公网地址 4. 固定公网地址访问 总结 前言 SVN 是 subversion 的缩写&#xff0c;是一个开放源…

转录组学习第四弹-数据质控

数据质控 将SRR转为fastq之后&#xff0c;我们需要对fastq进行质量检查&#xff0c;排除质量不好的数据 1.质量检查&#xff0c;生成报告文件 ls *fastq.gz|while read id;do fastqc $id;done并行处理 ls *fastq.gz|xargs fastqc -t 102.生成 html 报告文件和对应的 zip 压缩…

electron使用electron-builder macOS windows 打包 签名 更新 上架

0. 前言 0.1 项目工程 看清目录结构&#xff0c;以便您阅读后续内容 0.2 参考资料 &#xff08;1&#xff09;macOS开发 证书等配置/打包后导出及上架 https://www.jianshu.com/p/c9c71f2f6eac首先需要为Mac App创建App ID&#xff1a; 填写信息如下—Description为"P…

(02)vite环境变量配置

文章目录 将开发环境和生产环境区分开环境变量vite处理环境变量loadEnv 业务代码需要使用环境变量.env.env.development.env.test修改VITE_前缀 将开发环境和生产环境区分开 分别创建三个vite 的配置文件&#xff0c;并将它们引入vite.config.js vite.base.config.js import…

Kubernetes Gateway API 攻略:解锁集群流量服务新维度!

Kubernetes Gateway API 刚刚 GA&#xff0c;旨在改进将集群服务暴露给外部的过程。这其中包括一套更标准、更强大的 API资源&#xff0c;用于管理已暴露的服务。在这篇文章中&#xff0c;我将介绍 Gateway API 资源&#xff0c;并以 Istio 为例来展示这些资源是如何关联的。通…

C语言scanf_s函数的使用

因为scanf函数存在缓冲区溢出的可能性&#xff1b;提供了scanf_s函数&#xff1b;增加一个参数&#xff1b; scanf_s最后一个参数是缓冲区的大小&#xff0c;表示最多读取n-1个字符&#xff1b; 下图代码&#xff1b; 读取整型数可以不指定长度&#xff1b;读取char&#xf…

VMware安装kali(详细版)

如果不详细&#xff0c;你就留言骂我&#xff01; 文章目录 前言一、安装VMware二、安装KALI安装KALI配置网络总结 前言 今天更VMware安装kali 一、安装VMware VMware网址 安装之前&#xff0c;建议先退出360、电脑管家等杀毒软件&#xff0c;Win10操作系统好像还需要检查一…

HTML5生成二维码

H5生成二维码 前言二维码实现过程页面实现关键点全部源码 前言 本文主要讲解如何通过原生HTML、CSS、Js中的qrcodejs二维码生成库&#xff0c;实现一个输入URL按下回车后输出URL。文章底部有全部源码&#xff0c;需要可以自取。 实现效果图&#xff1a; 上述实现效果为&#…

TensorFlow实战教程(十八)-Keras搭建卷积神经网络及CNN原理详解

从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前一篇文章详细讲解了Keras实现分类学习,以MNIST数字图片为例进行讲解。本篇文章详细讲解了卷积神经网络CNN原理,并通过Keras编写CNN实现了MNIST分类学习案例。基础性文章,希望对您有所帮助! 一…

[Linux] 进程入门

&#x1f4bb;文章目录 &#x1f4c4;前言计算机的结构体系与概念冯诺依曼体系结构操作系统概念目的与定位 进程概念描述进程-PCBtask_struct检查进程利用fork创建子进程 进程状态进程状态查看僵尸进程孤儿进程 &#x1f4d3;总结 &#x1f4c4;前言 作为一名程序员&#xff0c…

同为科技(TOWE)桌面PDU插排:一款可以DIY定制的“超级插座”

当今社会&#xff0c;各种电子产品和家用电器已成为人们日常生活中不可或缺的一部分&#xff0c;在带给人们便利的同时&#xff0c;也使得电力使用变得更加频繁和重要。然而&#xff0c;当前市面上很多普通插座由于功能单一、材质粗劣、插口数量受限、充电速度过慢、插头间互相…

【shell】 1、bash语法超详细介绍

文章目录 修改前缀路径dirname set常用函数参数变量local 返回值正则打印第 n 行获取行号核对数据库各表数量jq查询检查日志 sshpassexpect数组xargs bash manual 修改前缀 参考 export PS1"bash> "路径 dirname strip last component from file name dir$(…

CMap数据库筛选化学药物

数据库clue.io 文献链接&#xff1a;连接图谱&#xff1a;使用基因表达特征连接小分子、基因和疾病 |科学 (science.org) 基本模式&#xff1a;利用CMap将差异基因列表与数据库参考数据集比对&#xff1b;根据差异表达基因在参考基因表达谱富集情况得到一个相关性分数&#…

新加坡服务器托管-金融企业的选择

新加坡作为一个亚洲金融中心&#xff0c;其优越的地理位置和先进的信息通信技术基础设施&#xff0c;使得其成为了众多金融机构企业选择服务器机房托管的理想地点。金融行业对于服务器的安全性和可靠性要求很高&#xff0c;而将服务器托管在新加坡有许多好处。 首先&#xff0c…

复杂类型,查询--学习笔记

1&#xff0c;复杂类型 解决问题&#xff1a;一些不容易获取到的数据&#xff0c;例如数组类型&#xff0c;集合类型等&#xff0c;获取他们的数据 -- 1.创建表 create table tb_array_person(name string,city_array array<string> )row format delimited fields term…

HarmonyOS ArkTSTabs组件的使用(六)

Tabs组件的使用 ArkUI开发框架提供了一种页签容器组件Tabs&#xff0c;开发者通过Tabs组件可以很容易的实现内容视图的切换。页签容器Tabs的形式多种多样&#xff0c;不同的页面设计页签不一样&#xff0c;可以把页签设置在底部、顶部或者侧边。 Tabs组件的简单使用 Tabs组件…

网络参考模型与标准协议(二)-TCP/IP对等模型详细介绍

应用层 应用层为应用软件提供接口&#xff0c;使应用程序能够使用网络服务。应用层协议会指定使用相应的传输层协议&#xff0c;以及传输层所使用的端口等。TCP/IP每一层都让数据得以通过网络进行传输&#xff0c;这些层之间使用PDU ( Paket Data Unit,协议数据单元)彼此交换信…