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

news2024/11/16 17:51:58

文章目录

  • 前言
  • 正文
    • 一、前戏,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;
		}

		// 处理url
		String url = this.url + cleanPath();
		// 通过工厂获取client实例
		Client client = getOptional(feignClientFactory, Client.class);
		// 生成client
		if (client != null) {
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((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

默认实现如下:

	@Override
	public <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();
                // 获取方法,其实就是接口中的方法封装成了多个MethodMetadata
                Method method = md.method();
                if (method.getDeclaringClass() != Object.class) {
                	// 创建MethodHandler
                    InvocationHandlerFactory.MethodHandler handler = this.createMethodHandler(target, md, requestContext);
                    // map 映射method 和 handler
                    result.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.coloradmin.cn/o/1234313.html

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

相关文章

蓝桥杯物联网_STM32L071_1_CubMxkeil5基础配置

CubMx配置&#xff1a; project工程中添加.h和.c文件&#xff1a; keil5配置: 运行&#xff1a; 代码提示与解决中文乱码&#xff1a;

结合两个Python小游戏,带你复习while循环、if判断、函数等知识点

&#x1f490;作者&#xff1a;insist-- &#x1f490;个人主页&#xff1a;insist-- 的个人主页 理想主义的花&#xff0c;最终会盛开在浪漫主义的土壤里&#xff0c;我们的热情永远不会熄灭&#xff0c;在现实平凡中&#xff0c;我们终将上岸&#xff0c;阳光万里 ❤️欢迎点…

Google Chrome 任意文件读取 (CVE-2023-4357)漏洞

漏洞描述 该漏洞的存在是由于 Google Chrome 中用户提供的 XML 输入验证不足。远程攻击者可以创建特制网页&#xff0c;诱骗受害者访问该网页并获取用户系统上的敏感信息。远程攻击者可利用该漏洞通过构建的 HTML 页面绕过文件访问限制&#xff0c;导致chrome任意文件读取。Li…

Redis篇---第九篇

系列文章目录 文章目录 系列文章目录前言一、如果有大量的 key 需要设置同一时间过期,一般需要注意什么?二、什么情况下可能会导致 Redis 阻塞?三、缓存和数据库谁先更新呢?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击…

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

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

Selenium4+python被单独定义<div>的动态输入框和二级下拉框要怎么定位?

今天在做练习题的时候,发现几个问题捣鼓了好久,写下这篇来记录 问题一: 有层级的复选框无法定位到二级目录 对于这种拥有二级框的选项无法定位,也不是<select>属性. 我们查看下HTML,发现它是被单独封装在body内拥有动态属性的独立<div>,当窗口点击的时候才会触发…

【算法设计实验三】动态规划解决01背包问题

请勿原模原样复制&#xff01; 01背包dp具体解释详见链接 ↓ 【算法5.1】背包问题 - 01背包 &#xff08;至多最大价值、至少最小价值&#xff09;_背包问题求最小价值_Roye_ack的博客-CSDN博客 关于如何求出最优物品选择方案&#xff1f; 先在递归求dp公式时&#xff0c;若…

【广州华锐互动】VR溺水预防教育:在虚拟世界中学会自救!

在现代社会中&#xff0c;水上安全和救援行动的重要性不言而喻。尤其在自然灾害、游泳事故或航海事故中&#xff0c;有效的救援行动可以挽救许多生命。然而&#xff0c;传统的救援训练往往存在成本高、风险大、效率低等问题。在这样的背景下&#xff0c;虚拟现实&#xff08;VR…

vue3的两个提示[Vue warn]: 关于组件渲染和函数外部使用

1. [Vue warn]: inject() can only be used inside setup() or functional components. 这个消息是提示我们&#xff0c;需要将引入的方法作为一个变量使用。以vue-store为例&#xff0c;如果我们按照如下的方式使用&#xff1a; import UseUserStore from ../../store/module…

如何把A3 pdf 文章打印成A4

1. 用Adobe Acrobat 打开pdf 2 打印 选择海报 进行调整即可如下图,见下面红色的部分。

2024测试工程师必学的Jmeter:利用jmeter插件收集性能测试结果汇总报告和聚合报告

利用jmeter插件收集性能测试结果 汇总报告&#xff08;Summary Report &#xff09; 用来收集性能测试过程中的请求以及事务各项指标。通过监听器--汇总报告 可以添加该元件。界面如下图所示 汇总报告界面介绍&#xff1a; 所有数据写入一个文件&#xff1a;保存测试结果到本地…

Jieba库——中文自然语言处理的利器

中文作为世界上最广泛使用的语言之一&#xff0c;其复杂的结构和丰富的表达方式给中文文本处理带来了挑战。为了解决这些问题&#xff0c;Python开发者开发了一系列用于处理中文文本的工具和库&#xff0c;其中最受欢迎和广泛应用的就是Jieba库。Jieba是一个开源的中文分词工具…

设计模式—命令模式

1.什么是命令模式&#xff1f; 命令模式是一种行为型设计模式&#xff0c;核心是将每种请求或操作封装为一个独立的对象&#xff0c;从而可以集中管理这些请求或操作&#xff0c;比如将请求队列化依次执行、或者对操作进行记录和撤销。 命令模式通过将请求的发送者&#xff0…

京东数据分析软件(京东平台数据分析):2023年Q3扫地机器人行业消费报告

随着90后、00后逐渐成为消费主力军&#xff0c;他们对生活品质更加关注、健康意识进一步增强&#xff0c;再加上“懒人经济”的盛行&#xff0c;人们对扫地机器人的使用率和关注热情也不断增长。 根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;今年7月份-9月份&#xf…

软件测评中心进行安全测试有哪些流程?安全测试报告如何收费?

在当今数字化时代&#xff0c;软件安全测试是每个软件开发团队都不能忽视的重要环节。安全测试是指对软件产品进行系统、全面的安全性评测与检测的过程。它旨在发现并修复软件中存在的漏洞和安全隐患&#xff0c;以确保软件能够在使用过程中保护用户的数据和隐私不被非法访问和…

ML-Net:通过深度学习彻底改变多标签分类

一、说明 多标签分类是一项具有挑战性的机器学习任务&#xff0c;其中输入可以同时属于多个类。传统的多标签分类方法通常依赖于将问题转化为一系列二元分类任务或使用集成方法。然而&#xff0c;深度学习的出现开创了多标签分类的新时代&#xff0c;ML-Net 等模型突破了该领域…

Linux系统编程 系统编程概念

1.系统调用 系统调用&#xff08;system call&#xff09;其实是 Linux 内核提供给应用层的应用编程接口&#xff08;API&#xff09;&#xff0c;是 Linux 应用层进入内核的入口。不止 Linux 系统&#xff0c;所有的操作系统都会向应用层提供系统调用&#xff0c;应用程序通过…

(论文阅读51-57)图像描述3 53

51.文献阅读笔记&#xff08;KNN&#xff09; 简介 题目 Exploring Nearest Neighbor Approaches for Image Captioning 作者 Jacob Devlin, Saurabh Gupta, Ross Girshick, Margaret Mitchell, C. Lawrence Zitnick, arXiv:1505.04467 原文链接 http://arxiv.org/pdf/1…

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

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

SystemV

一、共享内存 1、直接原理 进程间通信的本质是&#xff1a;先让不同的进程&#xff0c;看到同一份资源&#xff01;&#xff01; 我们要把这句话奉若圭臬一般 到了共享内存了支持双向通信能读也能写&#xff0c;但是一般都是一个读一个写 要想通信先看到同一个份资源&#xff0…