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;
		}

		// 处理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的动态代理原理)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flutter笔记:使用相机

Flutter笔记 使用相机 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/134493373 【简介】本文介绍在 Fl…

用VS编译ROS包

扩展安装 在扩展中搜索并安装ROS、C、python、CMake和CMake Tools。 打开工作空间 文件→打开文件夹 新建功能包 右键src文件夹&#xff0c;选择新建功能包&#xff08;通常是最后一条命令&#xff09; 编译 如果需要新建终端的话&#xff0c;就点击下图中的加号 创建la…

Git 笔记之gitignore

解释为&#xff1a;git ignore 即&#xff0c;此类型的文件将会被忽略掉&#xff0c;从而不会进行管理 具体的模板可以从 GitHub 网站上来进行设置 GitHub - github/gitignore: A collection of useful .gitignore templates Common_gitignore: gitignoregithub开源项目&…

Spring-IOC-@Value和@PropertySource用法

1、Book.java PropertySource(value"classpath:配置文件地址") 替代 <context:property-placeholder location"配置文件地址"/> Value("${book.bid}") Value("${book.bname}") Value("${book.price}") <bean id&…

C++ STL -->string类

文章目录 STL什么是STL String类string类对象的构造方式string类对象的容量操作string类对象的访问及遍历操作string迭代器函数遍历类对象 stirng类对象的修改操作string类非成员函数 STL 什么是STL STL全称standard template libaray-标准模板库 是C标准库的重要组成部分 不…

枚举 小蓝的漆房

题目 思路 核心思想是枚举 首先利用set记录每一种颜色 然后依次从set取出一种颜色作为targetColor&#xff0c;遍历房子 如果当前房子的颜色和targetColor不相同&#xff0c;就以当前房子为起点&#xff0c;往后长度为k的区间都涂成targetColor&#xff0c;并且需要的天数递增…

华为云cce健康检查有什么用?配置需要注意什么?

华为云cce健康检查 如上图&#xff0c;华为云健康检查可用来探测cce的实例运行状态&#xff0c;必要时cce会自动重启实例&#xff0c;达到cce持续服务。 但是配置时需要注意一下几个方面&#xff0c;否则cce的状态总是有些不正常。 1、http探查比较友好。因为我们的在cce里面…

深入理解强化学习——马尔可夫决策过程:马尔可夫决策过程和马尔可夫过程/马尔可夫奖励过程的区别

分类目录&#xff1a;《深入理解强化学习》总目录 《深入理解强化学习——马尔可夫决策过程》系列前面的文章讨论到的马尔可夫过程和马尔可夫奖励过程都是自发改变的随机过程&#xff0c;而如果有一个外界的“刺激”来共同改变这个随机过程&#xff0c;就有了马尔可夫决策过程&…

【ctfshow】web入门-信息搜集-web11~20

【ctfshow】web入门-信息搜集-web11~17 web11_域名其实也可以隐藏信息&#xff0c;比如flag.ctfshow.com 就隐藏了一条信息web12_有时候网站上的公开信息&#xff0c;就是管理员常用密码web13_技术文档里面不要出现敏感信息&#xff0c;部署到生产环境后及时修改默认密码web14_…

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

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

低代码服务商,中小型数字化软件服务商的新出路

数字化时代大背景下&#xff0c;企业信息化向数字化转型成为所有企业发展的必由之路&#xff0c;企业在对业务模式、流程、组织形式、信息技术等方面进行重新定义时&#xff0c;软件必然参与价值创造的全过程&#xff0c;这势必驱使软件成为推动数字化转型的“引擎”&#xff0…

Motion Plan之搜索算法笔记

背景&#xff1a; 16-18年做过一阵子无人驾驶&#xff0c;那时候痴迷于移动规划&#xff1b;然而当时可学习的资料非常少&#xff0c;网上的论文也不算太多。基本就是Darpa的几十篇无人越野几次比赛的文章&#xff0c;基本没有成系统的文章和代码讲解实现。所以对移动规划的认…

React结合antd5实现整个表格编辑

通过react hooks 结合antd的table实现整个表格新增编辑。 引入组件依赖 import React, { useState } from react; import { Table, InputNumber, Button, Space, Input } from antd;定义数据 const originData [{ key: 1, name: 白银会员, value: 0, equity: 0, reward: 0…

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

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

MSTP配置示例

多生成树可以实现链路的防环、冗余备份、负载均衡功能 拓朴如下&#xff1a; 主要配置如下&#xff1a; [R1] interface GigabitEthernet0/0/1ip address 10.1.1.254 255.255.255.0 # interface GigabitEthernet0/0/2ip address 10.2.2.254 255.255.255.0 #[S1] interface …

如何利用CHATGPT写主题文章

问CHAT&#xff1a;新课标下畅言智慧课堂助力小学生量感培养&#xff0c;拟解决的关键问题 CHAT回复&#xff1a; 1. 确定智慧课堂在新课标下的正确应用方法&#xff1a;新课标对教育方法、内容等提出了新的要求&#xff0c;需要探讨如何将智慧课堂与新课标相结合&#xff0c;…

学习Rust适合写什么练手项目?【云驻共创】

Rust是一门备受关注的系统级编程语言&#xff0c;因其出色的内存安全性、高性能和并发性能而备受赞誉。对于那些希望学习和掌握Rust编程语言的人来说&#xff0c;练手项目是一个不可或缺的环节。通过实际动手完成项目&#xff0c;你可以加深对Rust语言特性和最佳实践的理解&…

JOSEF约瑟 数显电压继电器 HYJY-30-02 AC220V 导轨安装

HYJY系列电压继电器 HYJY-30-01集成电路电压继电器 HYJY-30-01A HYJY-30-01B HYJY-30-02集成电路电压继电器 HYJY-30-02A HYJY-30-02B HYJY-30-03-3集成电路电压继电器 HYJY-30-03-2 HYJY-30-03-1 HYJY-30-02电压继电器&#xff08;以下简称继电器&#xff09;用于发…

go语言学习之旅之Go 语言指针

学无止境&#xff0c;今天继续学习go语言的基础内容 Go语言支持指针&#xff0c;允许你在程序中直接操作变量的内存地址。指针存储了变量的内存地址&#xff0c;通过指针&#xff0c;你可以直接访问或修改该地址上的值。 学习过c语言的一定知道指针 定义指针 在Go语言中&…

基于非洲秃鹫算法优化概率神经网络PNN的分类预测 - 附代码

基于非洲秃鹫算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于非洲秃鹫算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于非洲秃鹫优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…