Spring-Cloud-OpenFeign源码解析-03-FeignClientFactoryBean

在Spring-Cloud-OpenFeign源码解析-02-OpenFeign自动装配分析到OpenFeign 接口代理对象的创建是通过构建成一个 FeignClientFactoryBean 对象,并最后注入到容器中的,那么这个FeignClientFactoryBean是如何实现代理对象创建的呢?

FactoryBean

public interface FactoryBean<T> {String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";@NullableT getObject() throws Exception;@NullableClass<?> getObjectType();default boolean isSingleton() {return true;}

FactoryBean是Spring框架中的一个扩展接口,用于创建和管理其他Bean实例的对象。使用它可以生成某些需要复杂初始化过程的bean对象。当配置某个bean实现了FactoryBean接口时,该bean返回的对象不是FactoryBean本身,而是FactoryBean#getObject()方法返回的对象,这就提供了我们自定义创建对象的能力。

它与Spring其他bean的主要区别在于,FactoryBean负责产生其他bean实例。也即当我们从IOC容器中获取一个FactoryBean时,我们得到的是它创建的那个bean的实例,而不是FactoryBean的实例本身。

FeignClientFactoryBean
public class FeignClientFactoryBeanimplements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {@Overridepublic Object getObject() {//调用getTarget()方法return getTarget();}/*** @param <T> the target type of the Feign client* @return a {@link Feign} client created with the specified data and the context* information*/<T> T getTarget() {FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class): applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);// 判断当前FeignClient注解中的url是否为空,如果不为空,直接通过url的调用if (!StringUtils.hasText(url)) {if (LOG.isInfoEnabled()) {LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");}//是否以http开头if (!name.startsWith("http")) {url = "http://" + name;}else {url = name;}url += cleanPath();//返回目标对象return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));}if (StringUtils.hasText(url) && !url.startsWith("http")) {url = "http://" + url;}String url = this.url + cleanPath();Client client = getOptional(context, Client.class);if (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(context, builder);Targeter targeter = get(context, Targeter.class);//返回目标对象return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));}
}protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {Client client = getOptional(context, Client.class);if (client != null) {builder.client(client);applyBuildCustomizers(context, builder);Targeter targeter = get(context, Targeter.class);//返回目标对象return targeter.target(this, builder, context, target);}throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");}

最终都会调用到targeter.target()方法

Targeter#target()

默认调用的是DefaultTargeter

class DefaultTargeter implements Targeter {@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,Target.HardCodedTarget<T> target) {//继续调用target方法return feign.target(target);}
}

feign.target()

public abstract class Feign {public <T> T target(Target<T> target) {//创建代理对象return build().newInstance(target);}//构建Feign对象public Feign build() {Client client = Capability.enrich(this.client, capabilities);Retryer retryer = Capability.enrich(this.retryer, capabilities);List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream().map(ri -> Capability.enrich(ri, capabilities)).collect(Collectors.toList());Logger logger = Capability.enrich(this.logger, capabilities);Contract contract = Capability.enrich(this.contract, capabilities);Options options = Capability.enrich(this.options, capabilities);Encoder encoder = Capability.enrich(this.encoder, capabilities);Decoder decoder = Capability.enrich(this.decoder, capabilities);//创建代理对象的InvocationHandler工厂实例InvocationHandlerFactory invocationHandlerFactory =Capability.enrich(this.invocationHandlerFactory, capabilities);QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);ParseHandlersByName handlersByName =new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,errorDecoder, synchronousMethodHandlerFactory);//最终返回ReflectiveFeign实例return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);}
}

ReflectiveFeign.newInstance()

public class ReflectiveFeign extends Feign {@Overridepublic <T> T newInstance(Target<T> target) {Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();//解析方法,封装为MethodHandlerfor (Method method : target.type().getMethods()) {//Object方法if (method.getDeclaringClass() == Object.class) {continue;//Default方法} else if (Util.isDefault(method)) {DefaultMethodHandler handler = new DefaultMethodHandler(method);defaultMethodHandlers.add(handler);methodToHandler.put(method, handler);} else {methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));}}InvocationHandler handler = factory.create(target, methodToHandler);//jdk动态代理创建对象T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}//返回代理对象return proxy;}
}

总结

通过创建FeignClientFactoryBean对象,在@Autowired或者@Resource注入FeignClient实例的时候,实际上返回的是FactoryBean#getObject()方法创建的对象,底层通过JDK动态代理Proxy.newProxyInstance()返回代理对象,具体的实现逻辑在InvocationHandler的invoke方法中

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

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

相关文章

基于EKF扩展卡尔曼滤波的一阶环形倒立摆控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于EKF扩展卡尔曼滤波的一阶环形倒立摆控制系统simulink建模与仿真。基于扩展卡尔曼滤波&#xff08;Extended Kalman Filter, EKF&#xff09;的一阶环形倒立摆控制系统&…

docker学习和常用命令

参考视频&#xff1a;05.Docker基础-常见命令_哔哩哔哩_bilibili 镜像仓库datahub docker命令官方文档&#xff1a;docker exec | Docker Docs systemtrl docker 启动docker systemtrl start docker停止docker systemtrl stop docker重启docker systemtrl restart docker设…

【并发程序设计】4. exec函数族

4.exec函数族 exec函数族是一组用于在进程中启动另一个程序来替换当前进程的函数。 exec函数族主要用于在当前进程内部执行一个新的程序&#xff0c;而不会创建新的进程。 子进程调用exec函数&#xff0c;族父进程不受影响。进程当前内容被指定的程序替换&#xff0c;但进程…

【YOLOV5 入门】——Gradio搭建Web GUI

引入&#xff1a;上节搭建的UI可视化界面只能以运行程序弹出窗口的形式运行&#xff0c;不能在网页Web中使用&#xff0c;本次代码将会非常少&#xff01; 一、Gradio简介与安装 Gradio 是一个用于构建机器学习模型演示界面和Web应用的开源库。提供了简单易用的界面&#xff0…

云端的艺术革命:云渲染如何重塑动画与视觉特效产业

在 2019 年&#xff0c;乔恩费儒&#xff08;Jon Favreau&#xff09;决定重拍迪士尼的经典电影《狮子王》。他的创新构想是以真实动物为模型&#xff0c;在非洲草原上拍摄&#xff0c;由真实动物“出演”的辛巴和其他角色&#xff0c;随后通过配音赋予它们生命。 为了实现这一…

janus源码分析(1)--代码结构整理

基础说明 janus官网 https://janus.conf.meetecho.com/index.html janus源码地址 https://github.com/meetecho/janus-gateway 编译及部署参考 https://pro-hnb.blog.csdn.net/article/details/137730389?spm1001.2014.3001.5502 https://pro-hnb.blog.csdn.net/article/deta…

flutter常用的指令(签名、adb、shell、keytool、scrcpy)

flutter常用的指令(签名、adb、shell、keytool、scrcpy) run 指定main_dev.dart debug flutter run -t lib/main_dev.dart --debug指定main_dev.dart release flutter run -t lib/main_dev.dart --release插件用到非空安全情况 flutter run -t lib/main_dev.dart --releas…

Linux mtoolstest命令教程:如何测试和显示mtools配置(附实例详解和注意事项)

Linux mtoolstest命令介绍 mtoolstest是一个用于测试mtools配置文件的命令。只需键入mtoolstest即可调用它&#xff0c;无需任何参数。mtoolstest会读取mtools配置文件&#xff0c;并将累积配置打印到标准输出。输出可以作为配置文件本身使用。 Linux mtoolstest命令适用的Li…

【Python探索之旅】列表

目录 特点 入门 访问元素 新增元素 修改元素 插入元素 删除元素 完结撒花 前言 在Python中&#xff0c;列表(List)是最常用的数据结构之一&#xff0c;类似于其他语言&#xff0c;如Java&#xff0c;与其不同啊Python中不需要声明数据类型。它提供了一种灵活且高效的方式…

Linux-线程

目录 1. 线程概念 2. 线程vs进程 3. 线程的优缺点 4. 线程创建 4.1 pthread_create 4.2 pthread_self 5. 线程终止 5.1 return 5.2 pthread_exit 5.3 pthread_cancel 6. 线程等待 7. 线程分离 1. 线程概念 线程&#xff1a;轻量级进程&#xff0c;在进程内部执行&a…

谷歌Gemini时代来了!加固搜索护城河、赋能全家桶,Gemini 1.5 Pro升级至200万token

3 月中旬&#xff0c;谷歌宣布 Google I/O 定档北京时间 5 月 15 日凌晨 1 点。而当大会开幕时间临近&#xff0c;本应是讨论度最高的时候&#xff0c;「宿敌」OpenAI 却半路杀出&#xff0c;抢先一天&#xff0c;仅耗时 27 分钟就发布了颠覆性巨作 GPT-4o&#xff0c;将新一轮…

PyTorch中定义自己的数据集

文章目录 1. 简介2. 查看PyTorch自带的数据集(可视化)3. 准备材料3.1 图片数据3.2 标签数据 4. 方法 1. 简介 尽管PyTorch提供了许多自带的数据集&#xff0c;如MNIST、CIFAR-10、ImageNet等&#xff0c;但它们对于没有经验的用户来说&#xff0c;理解数据加载器的工作原理以及…

【启程Golang之旅】环境设置、工具安装与代码实践

欢迎来到Golang的世界&#xff01;在当今快节奏的软件开发领域&#xff0c;选择一种高效、简洁的编程语言至关重要。而在这方面&#xff0c;Golang&#xff08;又称Go&#xff09;无疑是一个备受瞩目的选择。在本文中&#xff0c;带领您探索Golang的世界&#xff0c;一步步地了…

C++ LCR 089. 打家劫舍

文章目录 一、题目描述二、参考代码 一、题目描述 一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响小偷偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系…

数据建模简介

数据建模是指创建数据模型的过程&#xff0c;它定义了数据的结构、关系、规则和约束。数据模型为数据管理、存储、检索和使用提供了逻辑框架和基础。数据建模在数据库设计、数据仓库构建以及数据管理项目中扮演着重要角色。下面介绍数据建模的主要概念、类型、步骤和工具。数据…

【Web后端】MVC模式

1、简介 MVC模式&#xff0c;全称Model-View-Controller&#xff08;模型-视图-控制器&#xff09;模式&#xff0c;是一种软件设计典范&#xff0c;它将应用程序的用户界面&#xff08;视图&#xff09;和业务逻辑&#xff08;模型&#xff09;分离&#xff0c;同时提供了一个…

K8S内容

K8S介绍 1、故障迁移:当某一个node节点关机或挂掉后&#xff0c;node节点上的服务会自动转移到另一个node节点上&#xff0c;这个过程所有服务不中断。这是docker或普通云主机是不能做到的 2、资源调度:当node节点上的cpu、内存不够用的时候&#xff0c;可以扩充node节点&…

【DevOps】全面解析SMTP、POP3、IMAP协议及内网邮件服务器的搭建指南

目录 一、SMTP 1、简介 2、SMTP 的主要功能 3、SMTP 工作原理 4、SMTP 端口 5、SMTP 安全性 6、SMTP 的优缺点 二、POP3&#xff08;Post Office Protocol 3&#xff09; 1、简介 2、工作原理 3、特点 4、缺点 三、IMAP&#xff08;Internet Message Access Proto…

Next 学习-1

创建一个 Next.js 应用,node版本要高&#xff0c;16.5以上 npm淘宝镜像切为https://registry.npmmirror.com npm config set registry https://registry.npmmirror.com npx create-next-applatest//安装后 使用npm run dev 启动 Next.js 是围绕着 页面&#xff08;pages&am…

​​​【收录 Hello 算法】6.2 哈希冲突

目录 6.2 哈希冲突 6.2.1 链式地址 6.2.2 开放寻址 1. 线性探测 2. 平方探测 3. 多次哈希 6.2.3 编程语言的选择 6.2 哈希冲突 上一节提到&#xff0c;通常情况下哈希函数的输入空间远大于输出空间&#xff0c;因此理论上哈希冲突是不可避免的。比如&a…