【Spring源码】插播一个创建代理对象的wrapIfNecessary()方法

news2024/11/18 23:44:08

在文章【分析向】没有三级缓存会导致什么? 中,提到过一个方法——wrapIfNecessary(),就是在这个方法中为Bean创建的代理对象,介于篇幅原因,当时并咩有详细🔎分析这个方法,这篇文章我们进去wrapIfNecessary()这个方法中瞅瞅(。・ω・。)ノ

wrapIfNecessary()

wrapIfNecessary()方法一开始会依次进行一系列的判断,如果满足了任意的一个,就会直接返回传入的bean对象,都不满足才会开始代理对象的创建

一开始会先判断当前传入的bean对象是否已经处理过(从集合targetSourcedBeans中查询,能查询到说明当前bean已经被处理过了),如果处理过了,就直接返回

对于已经创建过代理对象也会直接返回(从集合advisedBeans中查询📖,如果查得到,就说明缓存中存在当前bean的代理对象,即当前bean已经创建过代理对象了)

接下来的两个方法也是继续判断是否需要创建代理的

isInfrastructureClass()

用于判断当前bean是否是Spring自带的bean(自带的bean无需进行代理)

这里对4个类进行了判断:

  • Advice.class

  • Pointcut.class

  • Advisor.class

  • AopInfrastructureBean.class

isAssignableFrom()

通过调用上面4个类的isAssignableFrom()方法来判断传入对象所代表的类或者接口是否与他们相同,或者是其超类或者超接口,如果是的话返回true,否则返回false

shouldSkip()

判断当前bean是否需要被略过

以上两个条件,满足任意一个,则将当前bean进行缓存,并返回

当前(。・ω・。)ノ

如果上面👆的条件都不满足,则要开始为当前bean创建代理对象了

getAdvicesAndAdvisorsForBean()

先通过调用getAdvicesAndAdvisorsForBean()方法获取当前bean的Advices和Advisors

findEligibleAdvisors()

在findEligibleAdvisors()方法中,从候选的通知器中找到合适正在创建的实例对象的通知器获取到的Advisors(获取当前系统中所有切面类的切面逻辑,封装成一个List对象)进行判断,看其切面定义是否能应用到当前Bean,在进行相关的拓展、排序操作,最终得到最终需要应用的Adviser

findCandidateAdvisors()

将当前系统中的切面类的切面逻辑进行封装,从而得到目标Advisors

进入其实现类

findAdvisorsThatCanApply()

对获取到的所有Advisor进行判断,看其切面定义是否可以应用到当前bean,从而得到最终需要应用的Advisors

通过同名方法findAdvisorsThatCanApply()从候选的通知器中找到合适正在创建的实例对象

在findAdvisorsThatCanApply()的一开始定义一个合适的增强器集合对象eligibleAdvisors,接着循环我们候选的增强器对象,判断我们的增强器对象是不是实现了IntroductionAdvisor(是否有引介增强),最后调用canApply()方法判断增强器是否适用于当前类型

canApply()

这里对Advisor的类型进行了判断(见末尾补充内容)

  • 如果是IntroductionAdvisor的话,则调用IntroductionAdvisor类型的实例进行类的过滤

  • 如果是PointcutAdvisor类型(我们用到的一般都是这个类型),转为PointcutAdvisor类型

再次进入同名方法,方法中先进行classFilter的natches方法校验,类型不匹配直接返回false

进行切点表达式的匹配判断最重要的就是ClassFilter和MethodMatcher这两个方法的实现。

在当前方法中,只有在类型匹配得上以后,才会再进行MethodMatcher方法级别的校验

这里顺便介绍下MethodMatcher中有两个matches方法:

  • 参数:Method、targetClass

  • 参数:Method、targetClass、Method的方法参数

他们两个的区别是:两个参数的matches是用于静态的方法,匹配三个参数的matches是在运行期动态的进行方法匹配的。

判断匹配器是不是IntroductionAwareMethodMatcher,创建一个集合用于保存targetClass的class对象,接着判断当前class是不是代理的class对象,如果不是就加入到集合中去;接着获取到targetclass所实现的接口的class对象并加入到集合中

接下来就是循环所有的class对象,通过class获取到所有的方法,循坏遍历获取到的方法,只要有一个方法能配到就会返回true

回到方法wrapIfNecessary()中,获取了当前bean的Advices和Advisors后,判断下获取到的集合advisors是否为🈳(🈳就是没找到),是就返回DO_NOT_PROXY,不为空则返回集合advisor s的数组对象

只要getAdvicesAndAdvisorsForBean()方法的返回值不是DO_NOT_PROXY,就会进入if条件语句的逻辑块,缓存当前bean的代理状态,然后调用createProxy()实现代理对象的创建,创建完成后,缓存生成的代理对象,随后一路返回,就获取到了的一个当前bean的代理对象

无论当前对象是否需要创建代理对象,都会先创建它的普通实例对象,只不过在后续的流程中会把这个普通的实例对象进行替换

ps:其实DO_NOT_PROXY的值就是null ⁄(⁄ ⁄ ⁄ω⁄ ⁄ ⁄)⁄

extendAdvisors()

提供hook方法,用于对目标Advisor进行扩展

sortAdvisors()

最后对需要代理的Advisors们按照一定的顺序进行排序

结束后将排好序的Advisors们返回

回到getAdvicesAndAdvisorsForBean()中,判断排好序的Advisors集合是否为空,空则返回“DO_NOT_PROXY”

如果排好序的Advisors的集合不等于“DO_NOT_PROXY”(不为空),就会开始为当前对象创建代理

createProxy()

createProxy()方法会创建代理对象并将该代理对象缓存并返回

首先,给bean定义设置暴露属性

exposeTargetClass()

主要功能就是设置ORIGINAL_TARGET_CLASS_ATTRIBUTEH的属性值

接着创建代理工厂、获取单当前类中相关属性,决定对于给定的bean是否应该使用targetClass而不是他的接口代理,检查proxyTargetClass设置以及preserverTargetClass属性

shouldProxyTargetClass()

如果proxyFactory.isProxyTargetClass()的值为false则会调用方法shouldProxyTargetClass()继续判断是使用JDK动态代理,还是CGLIB动态代理

再次进入同名方法

ORIGINAL_TARGET_CLASS_ATTRIBUTE这个变量表示的是Bean的定义属性,它可以表明一个给定的Bean是否应该被其目标类代理(在它首先被代理的情况下)。其值是TRUE或FALSE。如果代理工厂为一个特定的Bean建立了一个目标类代理,并希望强制Bean总是可以被投递到它的目标类(即使AOP建议通过自动代理被应用),那么代理工厂可以设置这个属性

evaluateProxyInterfaces()

evaluateProxyInterfaces()方法用于添加代理接口

一开始会初始化参数hasReasonableProxyInterface为false,然后判断目标类有没有实现的接口,如果有接口并且以下条件同时为true,则使用JDK动态代理,并将参数hasReasonableProxyInterface设为true

  • !isConfigurationCallbackInterface(ifc)

  • !isInternalLanguageInterface(ifc)

  • ifc.getMethods().length > 0

接下来如果确定使用JDK动态代理就会把所有目标类实现的接口都添加进代理工厂,如果不是就会把代理工厂的·屏proxyTargetClass属性设为true

添加完代理接口后,会开始构建增强器,设置要代理的目标类,随后进行代理的定制化(目前默认为空,就是啥都没干)

通过设置frozen属性控制代理工厂被配置后,是否还允许修改通知,默认值是false,最后调用getProxy()真正开始构建代理对象

getProxy()

此处会根据工厂的设置,创建一个可以反复调用的新的代理对象

createAopProxy()

创建代理工厂

添加或删除了接口,效果会有所不同。可以添加和删除拦截器。使用给定的类加载器(如果对代理的创建有必要)。

监听调用AdvisedSupportListener实现类的activated()方法

通过AopProxyFactory获取到AopProxy,这个AopProxyFactory是在初始化函数中定义的(DefaultAopProxyFactory)

再次进入同名方法中

这里首先选择使用哪种方式创建代理对象,如果满足以下3个条件中的任意一个,就会进入if条件代码块(不进入则使用JDK的提供的代理方式生成代理对象)

  • config.isOptimize() 是否对代理类的生成使用策略优化(默认为false)

  • config.isProxyTargetClass() 是否使用Cglib的方式创建代理对象(默认为false)

  • hasNoUserSuppliedProxyInterfaces(config) 目标类是否有接口存在且只有一个接口的时候接口类型不是SpringProxy类型(如下图)

接着从AdvisedSupport中获取目标类类对象,对目标类进行判断如果目标类是是接口或者Proxy类型的类则使用JDK的方式生成代理对象,否则使用CGLIB 进行动态代理

getProxy()

这个方法有两个实现

CglibAopProxy

我们先来看看CglibAopProxy的实现

从advised中获取ioc容器中配置的target对象,如果对象已经是CGLIB生成的,就取目标对象的父类作为目标对象的类,然后获取无法被代理的方法名(CGLIB的实现方式是继承,所以final、static修饰的方法不能被增强)

创建、配置增强器,配置其他信息(超类、代理类实现的接口、回调方法等)

这里有必要插播介绍下advisor、advice、adviced各自的含义

  • advisor 通知器包含advice和pointcut

  • advice 具体的某一个消息通知

  • adviced 用来配置代理(proxyFactory)

这部分代码中有个方法需要注意下AopProxyUtils中的completeProxiedInterfaces(this.advised)

此处的this.advised表示的是具体的代理工厂类也就是proxyFactory

completeProxiedInterfaces()

再次进入当前方法内的同名方法

根据AdvisedSupport类型中目标类的接口,如果目标类没有实现接口,就尝试获取目标类,如果目标类是接口,则添加到AdvisedSupport的接口中,如果是Proxy类型,也会尝试获取接口

最后将获取到的接口返回,回到方法getProxy()中,配置完属性后,回获取回调函数

getCallbacks()

首先对expose-proxy、isFrozen、isStatic这几个属性进行处理,接着将拦截器封装在DynamicAdvisedInterceptor中

new一个Callback类型的数组,将拦截器(回调函数)添加进数组,返回并赋值给变量callbacks

最后调用方法createProxyClassAndInstance() 通过Enhancer生成代理对象并设置返回

进入createClass()方法

详细看下createHelper()

preValidate()是校验callbackTypes、filter是否为空,以及为空时的处理

接着是一行特别长的代码,截图没截全,下面👇贴下代码

Object key = KEY_FACTORY.newInstance(
             this.superclass != null ? this.superclass.getName() : null,
             ReflectUtils.getNames(this.interfaces), 
             this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), 
             this.callbackTypes, 
             this.useFactory, 
             this.interceptDuringConstruction, 
             this.serialVersionUID);

这段代码是通过newInstance()方法来为创建的EnhancerKey对象的属性赋值

接着设置当前enhancer的代理类的key标识,然后调用父类即AbstractClassGenerator的创建代理类,生成代理对象后返回

啊啊啊~这篇长湿俺叻、搞定( ̄∇ ̄)/

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

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

相关文章

第三章 ArcGIS坐标系与投影变换

文章目录第一节 坐标系的概念1.1 坐标1.2 坐标系2 基准面介绍2.1 基准面概念2.2几种基准面的说明2.3 椭球体参数的区别3 坐标系的分类3.1 两种坐标系3.2 区别3.3 度(分、秒)和米的转换(高级)4 投影坐标系4.1 两种投影方法介绍4.2 …

5、判定法

定义 判定表法: 分析和表述若干输入条件下,被测对象针对这些输入做出响应的一种工具在遇到逻辑复杂的业务时,可以利用判定表理清期间的逻辑关系。 重要概念 条件: 条件桩:需求规格说明书定义的被测对象的所有输入条…

图解Attention

深度学习知识点总结 专栏链接: https://blog.csdn.net/qq_39707285/article/details/124005405 此专栏主要总结深度学习中的知识点,从各大数据集比赛开始,介绍历年冠军算法;同时总结深度学习中重要的知识点,包括损失函数、优化器…

面试官:JVM是如何判定对象已死的?

本文已收录至Github,推荐阅读 👉 Java随想录 知道的越多,才知知道的越少。——苏格拉底 文章目录引用计数算法可达性分析算法引用类型Dead Or Alive永久代真的"永久"吗?垃圾收集算法标记-清除算法标记-复制算法标记-整理…

网络编程基础

1 网络协议栈分层协议栈是指网络中各层协议的总和,反映了一个网络中数据传输的过程,由上层协议到底层协议,使用分层实现提高灵活性以及简化实现。OSI七层模型 和TCP/IP五层模型:物理层:考虑的是怎样才能在连接各种计算…

学长教你学C-day14-C语言文件操作

“我们的C语言学习也马上接近尾声了,今天我们来讲最后一个内容:C语言的文件夹操作。” “那么什么是文件呢?其实C语言里的文件是数据源的一种,最主要的作用是保存数据。例如txt、word、pdf等等都是不同的存储数据的形式。通过C语…

WebView与 JS 交互方式

一 前言 现在很多App里都内置了Web网页(Hybrid App),比如说很多电商平台,淘宝、京东、聚划算等等,如下图 上述功能是由Android的WebView实现的,其中涉及到Android客户端与Web网页交互的实现,今…

Vue路由使用的几个注意点

前言 在使用vue的路由的时候,是有几个需要注意的点,下面一一说明 组件的分类 组件分为两种:路由组件和一般组件 路由组件是注册到路由器中,并且是由路由相关标签代码进行展示 一般组件是注册到组件中,通过组件标签…

Linux常用命令——route命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) route 显示并设置Linux中静态路由表 补充说明 route命令用来显示并设置Linux内核中的网络路由表,route命令设置的路由主要是静态路由。要实现两个不同的子网之间的通信,需要一台连接两个…

【软件架构思想系列】从伟人《矛盾论》中悟到的软件架构思想真谛:“对象”即事物,“函数”即运动变化...

引子 形而上学和辩证法两种宇宙观是截然相反的。“所谓形而上学的或庸俗进化论的宇宙观,就是用孤立的、静止的和片面的观点去看世界。这种宇宙观把世界一切事物,一切事物的形态和种类,都看成是永远彼此孤立和永远不变化的”,“和形而上学的宇宙观相反,唯物辩证法的宇宙观主…

元宇宙与数字孪生有区别

在元宇宙爆红之前,有一项技术已经慢慢渗透到各行各业之中,它可以逼真、实时地还原现实世界,它就是——数字孪生。目前很多人认为元宇宙与数字孪生的区别不大,元宇宙是数字孪生在技术层面的进阶与优化。其实不然,元宇宙…

IMX Linux 用户手册 --- 2

IMX Linux 用户手册 — 2 第5章 启用单独仿真 可以在i.MX 6DualLite SABRE-SD和i.MX 6DualLite SABRE-AI板上启用单独仿真。这是通过使用 引导加载程序构建过程中的特定U-Boot配置。 当在i.MX 6DualLite SABRE平台上启用此单独仿真时,i.MX 6DualLite的功能将更改为…

高通开发系列 - MSM8909指示灯操作

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 最近在做内核升级,但是内核串口log始终无法打印,也不清楚是不是内核跑飞了还是什么原因,先尝试点亮一个LED灯来判断下。 这里面我们…

docker start启动容器不报错,却无法正常启动

问题描述:想启动 1e 这个容器 start后,没有报错,但是就是启动不了… 原因 查看一下日志 docker logs 1e1b85322dfa好家伙,虽然它运行命令的时候不报错,但是它运行错误信息写到日志里面去了,查看最新的日记信息看…

回收租赁商城系统功能拆解14讲-分销设置

回收租赁系统适用于物品回收、物品租赁、二手买卖交易等三大场景。 可以快速帮助企业搭建类似闲鱼回收/爱回收/爱租机/人人租等回收租赁商城。 回收租赁系统支持智能评估回收价格,后台调整最终回收价,用户同意回收后系统即刻放款,用户微信零…

MySQL8.0版本怎样进行CentOS系统配置?

MySQL安装完成后,会自动配置为名称叫做:mysqld的服务,可以被systemctl所管理,我们在进行系统的配置时,主要修改root密码和允许root远程登录。 # 通过grep命令,在/var/log/mysqld.log文件中,过滤…

深度学习论文: Multi-modal Sensor Fusion for Auto Driving Perception: A Survey

深度学习论文: Multi-modal Sensor Fusion for Auto Driving Perception: A Survey Multi-modal Sensor Fusion for Auto Driving Perception: A Survey PDF: https://arxiv.org/pdf/2202.02703.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代码: h…

图论中的GLM模型

下面是我对GLM模型的理解: 数据编码的方式 在一般统计中,常用的coding方式有dummy,effect和cell.mean,这个在R和python中都可以实现。 dummy coding 举例 假设有4个组别A, B, C, D,它的自由度是4-13,因此…

自动驾驶专题介绍 ———— APA标准(ISO 16787)

文章目录介绍通用要求泊车控制最大运行速度退出条件建议空间车位水平空间车位垂直空间车位泊车流程流程定义Queiscent ModeSearch ModeSlot FoundMode SelectionAssisted Parking ModeEnd of Assisted Parking Mode人机交互策略车位搜索阶段车位搜索到且未激活泊车驾驶员请求泊…

【手写 Promise 源码】第七篇 - 实现 Promise 返回值 x 的处理

一,前言 上篇,实现了 Promise 的链式调用功能,主要涉及到以下几个点: 介绍了 Promise 的链式调用,返回普通值和抛出异常的共5种情况;分析了当前 Promise 源码的问题以及解决方案;Promise 链式…