Spring源码系列-Spring AOP

 

 

AOP 要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。

AOP 的实现并不是因为 Java 提供了什么神奇的钩子,可以把方法的几个生命周期告诉我们,而是我们要实现一个代理,实际运行的实例其实是生成的代理类的实例 

f1b9924ed8f644a698e966410bfd0964.png

5a87bc01ec9940d9808400514a6ef01e.png

Advisor是源码中的概念,应用本身不需要关心。织入,就是通过动态代理来实现将增强逻辑织入到目标对象的目标方法上去的 

 

AOP的用法

注解用法

dd1dd54f903a4d8eb7719c80d9939cbb.png

1372797fc65140e7bf27a3b86c61bda5.png

 

dd020a33df9f46bc93006cfe1c826d80.png

SpringAop只用了AspectJ的注解,还有切点定义切点解析之类的东西

Spring AOP只能用户ioc容器中的bean实例的方法级别进行增强,如果不是ioc容器中的bean,那么spring AOP就无能为力了

 

早期的基于接口的用法

基于接口的各种类型的通知Advice

早期没有引入AspectJ的切点、切面这一套时,是没有切面的

1f2dcf9c08ed4d338193aa37d9151e5a.png

8ecad0a9928e4213b54df9f5628e7888.png

这里是通过方法拦截器的方式,来给目标方法做一些增强;

方法拦截器的这种形式,可以理解成为环绕通知;

0b8477bf25a64055a126b118bc846c64.png

 

99b0337b5b7043b89d8399716c7eb683.png

实现AOP,可以使用Advice通知,也可以使用方法拦截器,因为它们有共同的父类Advice;方法拦截器,也是Advice接口的子实现

 

04ba9e506da141f0aa6aff34fd71f63d.png

 这里就是给目标方法,指定了两个通知,tulingLogAdvice和tulingLogInterceptor(tulingLogAdvice和tulingLogInterceptor也就是通过@Bean注入到ioc容器中的两个bean),传入了两个通知,这两个通知就形成了一个通知链

此时,用法非常的死板,只能一次针对一个Bean来创建动态代理对象,如果我有十个bean需要生成动态代理,那么这里就要显示的定义十个不同的ProxyFactoryBean

f8ee72ad9263401d86ccd8ef117c933e.png

执行结果:

0a7efc679f8e416e8a1dbd6a41f4c96c.png

通过这种方式创建一个动态代理对象,并使用

 

ProxyFactoryBean#getObject()

就是通过内部生成一个不同通知的责任链,来依次调用不同的通知

45cabb7396354248ac00c13f8df26a2b.png

可以看到,这里252行在创建动态代理对象之前,首先就是初始化出来一个通知链

 

递归实现的责任链模式

 

简单实现

a1de9ec861ac4db8ac228fe0927dd2f5.png

第23行,因为TulingLogAdvice没有实现MethodInterceptor接口,所以我们又自定义了一个委托MethodBeforeAdviceInterceptor

e3bcac8e32ca4f65bf3c7708ed556c71.png

可以看到,这里是适配器和责任链的组合,不同的拦截器形成的责任链。这里,使用的是递归形式的责任链

 

2118776052f141e38ea3f79c97926b8b.png

这里就采用的是,“递归 + 列表索引”的方式,来实现的责任链模式这里的MyMethodInvocation就相当于,标准责任链模式中的FilterChain,FilterChain中也有一个List<Filter>,这里MyMethodInvocation是有一个List<MethodInteceptor

 

责任链模式就两个关键点

  • 统一的节点抽象,继承统一的父类或者实现统一的接口,从而方便统一的调用
  • 通过递归,循环,或者next指针的方式来进行调用

 

04ba9e506da141f0aa6aff34fd71f63d.png

可以看到这里56行,是把整个目标对象都传递了进入,而不是传递的目标对象的具体目标方法,这样就会导致写了一套增强逻辑,这套增强逻辑就会对目标对象的每个方法都生效

 

传统的aop实现方式的局限性

  • 问题一,不能精确到方法级别的aop增强,而是为类中的每个方法都进行了增强
  • 问题二,需要创建很多FactoryBean,针对每个目标对象都要创建一个FactoryBean

 

Advisor

 

解决不能精确到方法级别的增强

因为问题一,不能精确到方法级别的aop增强,引入了Advisor的概念

f81975367ce04cbbb86815407e36c5ff.png

这里一个我们通过接口自定义的Advice,就被封装成了一个的Advisor,后面每个定义的不同的Advice都会被封装成了一个个的Advisor

后面通过注解定义的aop,这里每个@Before、@After,也都会被封装成一个个的Advisor

599dfb1df44a428989ed9d553d5210c0.png

按照正则表达式匹配,或者按照方法名进行匹配,

执行结果:

9c47dee6a0ad4038aa9deddf722c5394.png

这里,就精确到了目标对象的div方法

 

f1d560c94fbb4816b868eea171203ca6.png 26c554be24be4ef2bbbe9157d1a9d0dd.png

 

所以,有了Advisor后就能让aop增强精确到方法级别,Advisor的作用:

  • 包含前置通知、后置通知等增强逻辑
  • 指定要增强的方法名

注意,Advisor并不知道目标对象是谁,目标对象还需要单独指定,代表要把Advisor的增强逻辑附加在哪些目标对象之上

 

注解形式的Advisor

804b8222d84a4c538b25ccce9268c30c.png

后面通过注解定义的aop,这里每个@Before、@After都会被封装成一个个的Advisor 

c159413fda0c4a369b3c66c83814828d.png

 

解决需要创建多个FactoryBean

问题二:需要创建很多FactoryBean,从而引入BeanPostProcessor

因为每创建一个目标对象的动态代理,就要重新创建一个ProxyFactoryBean。通过ProxyFactoryBean这种手动的方式来创建动态代理对象

bcc121989e1341f19114bc92804219d7.png

通过BeanNameAutoProxyCreator这个BeanPostProcessor这种方式,动态扫描ioc中的所有bean,只要这个bean的beanName是以“tuling”开头的,那么就给这个bean来创建动态代理对象,动态代理的逻辑就是tulingLogAspectAdvisor中的通知逻辑(注意,这里使用了beanName通配符),指定的tuling*实际上也就是指定目标对象(Advisor本身并不知道目标对象是谁,目标对象还需要单独指定,代表要把Advisor的增强逻辑附加在哪些目标对象之上

 

纯注解的AOP实现原理

纯注解的方式的aop的实现原理,每个通知,都对应创建一个Advisor,每个Advisor中都有自己专属的有Advice和Pointcut

fa19906c05854ac692265bf1cdb10344.png

因为当前,通过BeanPostProcessor来生成动态代理对象时,是以传入的每个Adisor为基本增强单位的

首先,扫描ioc中的所有bean,看哪些bean上被标注了@Aspect注解,如果被标注了,则把里面的@Before、@After等全部变成一个个的Advisor

其次,在ioc的getBean创建bean时,就会通过给BeanNameAutoProxyCreator这个BeanPostProcessor来创建目标对象的动态代理对象,动态代理的给原实例增强的逻辑,就是传进BeanNameAutoProxyCreator中的一个个Adisor。而这些Adisor也就是上面扫描出来的一个个Adisor

82b195c5e1ce4942baa0a54241475e43.png

 

ioc加载doCreateBean()时创建当前bean时,会先拿到提前解析好的所有的Advisor,然后循环所有的Advisor,一一和当前bean的配对,如果配对上,那么就需要给当前bean做动态代理增强,增强的逻辑就是匹配上的Advisor中Advice的逻辑

Advisor的PointCut有三种不同的类型

  • 按方法名的
  • 按正则表达式的
  • 按AspectJ表达式的

这里的PointCut的匹配策略有很多种,也就是策略模式,

 

 

AOP源码解析

Aop核心三个步骤

  • 解析切面
  • 创建动态代理
  • 调用代理方法

 

AOP入口

@EnableAspectJProxy

ff10355b59f24128ba09d8e897ef9ac5.png

以后,只要是看spring ioc集成什么自身组件,或者第三方组件,一般都会加上一个@EnableXxxxx的注解,我们要找这个组件入口,就从这个注解开始,这也就是spring的一个灵活性

@EnableXxxxx注解中又有一个@Import注解

4d4010f976394aa0a13756308c52786b.png

9a3e775d1fa5406ebfabf87a6de63234.png c982f2ba3f004257a78fcb184fe18d81.png

AnnotationAwareAspectJAutoProxxyCreator这个BeanPostProcessor,在这里被注册到ioc容器的beanDefinitionMap中去,然后ApplicationContext#refresh()中有一步registerBeanPostProcessor()就会实例化这个BeanPostProcessor。

后续这些实例化好的BeanPostProcessor,就会在9大BeanPostProcessor执行的时机被分别的调起

 

AOP中的三大BeanPostProcessor

6f38f4fac9f842778d065d7ddf2667c2.png

看这个实现类,实现的三个接口,就是实现aop的三个关键接口,实现这三个接口的作用分别是:

  • 生成动态代理对象
  • 解析切面
  • 解决循环依赖中aop动态代理对象生成问题

 

AspectJ 本身是不支持运行期织入的,日常使用时候,我们经常回听说,spring 使用了aspectJ实现了aop,听起来好像spring的aop完全是依赖于aspectJ
其实spring对于aop的实现是通过动态代理(jdk的动态代理或者cglib的动态代理),它只是使用了aspectJ的Annotation,并没有使用它的编译期和织入器

aspectJ是在编译期修改了方法(类本身的字节码被改了),所以可以很轻松地实现调用自己的方法时候的增强。
3)spring aop的代理必须依赖于bean被spring管理,所以如果项目没有使用spring,又想使用aop,那就只能使用aspectJ了(不过现在没有用spring的项目应该挺少的吧。。。)
4)aspectJ由于是编译期进行的织入,性能会比spring好一点

 

Spring Aop的三种实现方式

  • 基于接口的方式
  • 基于注解的方式
  • Xml配置的方式

 

解析切面类

 

AnnotationAwareAspectJAutoProxxyCreator

这个bean的后置处理器,实现了InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation()方法,也就是bean后置处理器的9次调用中的第一次,就是在这里完成Advisor的解析填充工作

后续BeanPostProcessor的postProcessAfterInitialization()方法中,就会循环遍历这些填充好的Advisor,一个个匹配,然后创建bean的动态代理

c61e87d3eeea463188f20e69eb0fc4d1.png

 

ef593e9740ca47579122732805270b98.png

可以看到这里,会执行9次Bean后置处理器的第一次调用,也就是在第一次调用这里会进行解析切面 

9d3444662c4d4f8a97cd7298d4cb6754.png

164c11ccd9c54c9580c2ac4a1cdc9616.png

解析切面是很耗费性能的,所以需要保证只解析一次,所以就创建了缓存,解析好后就缓存起来,下次就不解析了 

95cfa874dfb24315b31a8ecd6342f0b5.png

如果是这几个类就不需要解析了,因为我们只解析标注了@Aspect的类,要做的工作只是把 @Aspect的类中的一个个通知,解析成一个个的Advisor存入list中

a047a03015e94055895e38ad0c23e6ee.png

e29ce754f1af4b008a55fead51ca7fac.png

abae4b1d95c548f2bb1843bc9431456d.png

 

Spring代码的对老代码的兼容性

17793bcf72fc4ecd83ac5cc2b94c6f23.png

这段代码就是为了兼容老的代码(向下兼容),因为当前的注解实现的aop的方式,是不会往ioc容器中手动显示的注入Advisor接口的实现类对象的,所以这里就会找不到。如果不是为了兼容老代码,那么这段84行开始的代码,就可以删除了

先去容器中找有没有实现了Advisor接口的类,因为我们当前都已经使用全注解的方式了,所以自然就在ioc容器中找不到实现了Advisor接口的类了

470d2e8fa7c549839b6f0bef792828b6.png

上面传统的aop的实现方式时,是自己手动往ioc容器中注入一个Advisor类型的bean,如果这个时候,ioc容器中就能找到实现了Advisor接口的类了

 

 

97da8cb49c8a4f41a08df6cc148bd392.png

这里,就开始判断ioc中的bean,哪些bean有@Aspect注解,若果是切面类则把里面的所有通知都解析成一个个Advisor

431e4be0b0c64aabb8410e1ce0157bd9.png

判断当前类,是不是切面类

d3063e8e843047c4b47315c60bab7229.png

使用缓存,每个切面类的类名作为key,切面类下的List<Advisor>作为value

 

切面类

b69a90875be54dd18522fec5e6b5618d.png

168219d4c9254de8a8b1e06b77069ebc.png

会扫描所有标注了@Aspect注解的Bean,解析切面类中的每一个方法,只要这个bean的哪个方法上标注了@Before,@After等注解,就生成一个对应的Advisor

 

 

cglib动态代理和jdk动态代理

 

a6489ed82c83450f8ff59c0188fcdbc9.png

 

cglib生成的动态代理对象,调用自身的方法,也是要经过动态代理的逻辑

jdk动态代理对象的方法内部,调用自己的另一个方法,则不会走动态代理增强的逻辑 

jdk和cglib代理现在都是修改的字节码,所以现在两者性能方面都是差不多的

 

创建动态代理

d8de2f9b36354f6eb0c6266152458e8f.png

doCreateBean()内每个bean都会经历实例化、属性注入、初始化,在初始化后就会调用一堆的后置处理器的postProcessAfterInitialization()方法,当调用到AbstractAutoProxyCreator这个后置处理器的postProcessAfterInitialization()方法时,这个方法内部就会拿到前面解析并缓存好的所有的切面类的所有Advisors,然后循环遍历Advisors是否能匹配上当前bean,如果能匹配上就开始创建当前bean的动态代理对象,创建好的动态代理对象,就会交给ioc容器

1b50313052da42d1915b97f2cf214d37.png

 

调用代理方法

5f5bcfcbfa47429ab243462c73b6ff5a.png

7f6af1b5d2994b31b8621cf3424ec85a.png

88990862fdc247a5b3992a0b4eb3b40e.png

aop的运行原理就是,先把切面所有通知变成统一的Advisor,然后通过AspectJ的表达式匹配算法,判断出当前bean与哪些Advisor匹配,将这些匹配的Advisor们保存起来,在创建jdk动态代理对象时,将这些匹配的匹配的Advisor们,传入ReflctiveMethodInvocation中,然后就是通过递归调用,依次调用各个Advice,最后调到目标方法,然后再一步步的返回

 

f88d2be9d5544834bca033222402131c.png

d1475495942c49d7baeb27e5d1fd1154.png

不管是不是有异常抛出,后置通知都是会执行的,因为后置通知是写在finally 中的

 

疑问:

上面的流程,好像还是只到了类级别的控制,没有到方法级别的控制。

 

 

f5a869ca30ba4071ac81e662f9681284.png

jdk动态代理内一个方法直接调用另一个方法,是不会触发另一个方法的动态代理逻辑的,我们只有通过这种方法,先拿到动态代理对象,然后在调用动态代理的方法

651c68fb329347fa8e4b38f774fd6c8b.png

3b56b011575c48ba8720daf3652783b9.png

 



 

 

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

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

相关文章

Leetcode刷题详解—— 有效的数独

1. 题目链接&#xff1a;36. 有效的数独 2. 题目描述&#xff1a; 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的…

11/12总结

项目进度&#xff1a; 界面画了搜索机票&#xff0c;预定机票&#xff0c;搜索酒店&#xff0c;预定酒店&#xff0c; 然后是开始写这些功能的后端逻辑

RT-DTER 引入用于低分辨率图像和小物体的新 CNN 模块 SPD-Conv

论文地址:https://arxiv.org/pdf/2208.03641v1.pdf 代码地址:https://github.com/labsaint/spd-conv 卷积神经网络(CNN)在图像分类、目标检测等计算机视觉任务中取得了巨大的成功。然而,在图像分辨率较低或对象较小的更困难的任务中,它们的性能会迅速下降。 这源于现有CNN…

终端安全/SOC安全/汽车信息安全大课来袭-共计204节课

在近两年的时间里&#xff0c;我投入了大量的心血和精力&#xff0c;不仅创作了数千篇精美的图片&#xff0c;还编写了超过1000篇文章&#xff0c;以及数百篇内容丰富的PPT。经过这番努力我终于成功地构建出两套系统化的学习课程&#xff0c;它们分别是“Trustzone/TEE/安全从入…

Spring基础——初探

Spring是一个开源的Java应用程序开发框架&#xff0c;它提供了一个综合的编程和配置模型&#xff0c;用于构建现代化的企业级应用程序。Spring的目标是简化Java开发&#xff0c;并提供了许多功能和特性&#xff0c;以提供开发效率、降低开发复杂性。 特别 主要功能 IoC容器 …

IP多播需要使用两种协议(IGMP和多播路由选择协议)

目录 IGMP 多播路由选择协议 组播协议包括组成员管理协议和组播路由协议: 组成员管理协议用于管理组播组成员的加入和离开(IGMP) 组播路由协议负责在路由器之间交互信息来建立组播树(多播路由选择协议) IGMP 图中标有 IP 地址的四台主机都参加了一个多播组&#xff0c;其…

HCIP-双点双向重发布

实验拓扑 要求&#xff1a;全网没有次优路径、且尽量负载均衡。 设备配置 R1 [V200R003C00] #sysname R1 #snmp-agent local-engineid 800007DB03000000000000snmp-agent #clock timezone China-Standard-Time minus 08:00:00 # portal local-server load flash:/portalpage.…

Springboot快速入门

目录 一、概述 SpringBoot的特性 1、起步依赖 2、自动配置 3、其他特性 二、入门程序 步骤 1、创建Maven工程 2、配置spirng-boot-stater-web起步依赖 3、编写Controller 4、提供启动类 启动 测试 三、信息配置 四、整合mybatis 1、引入依赖 2、配置信息 3、…

php的api接口token简单实现

<?php // 生成 Token function generateToken() {$token bin2hex(random_bytes(16)); // 使用随机字节生成 tokenreturn $token; } // 存储 Token&#xff08;这里使用一个全局变量来模拟存储&#xff09; $tokens []; // 验证 Token function validateToken($token) {gl…

【数据结构】树与二叉树(十三):递归复制二叉树(算法CopyTree)

文章目录 5.2.1 二叉树二叉树性质引理5.1&#xff1a;二叉树中层数为i的结点至多有 2 i 2^i 2i个&#xff0c;其中 i ≥ 0 i \geq 0 i≥0。引理5.2&#xff1a;高度为k的二叉树中至多有 2 k 1 − 1 2^{k1}-1 2k1−1个结点&#xff0c;其中 k ≥ 0 k \geq 0 k≥0。引理5.3&…

pta 高空坠球 Python3

皮球从某给定高度自由落下&#xff0c;触地后反弹到原高度的一半&#xff0c;再落下&#xff0c;再反弹&#xff0c;……&#xff0c;如此反复。问皮球在第n次落地时&#xff0c;在空中一共经过多少距离&#xff1f;第n次反弹的高度是多少&#xff1f; 输入格式: 输入在一行中…

pyTorch Hub 系列#4:PGAN — GAN 模型

一、主题描述 2014 年生成对抗网络的诞生及其对任意数据分布进行有效建模的能力席卷了计算机视觉界。两人范例的简单性和推理时令人惊讶的快速样本生成是使 GAN 成为现实世界中实际应用的理想选择的两个主要因素。 然而&#xff0c;在它们出现后的很长一段时间内&#xff0c;GA…

11.12总结

这一周主要写了个人中心的几个功能&#xff0c;资料修改&#xff0c;收货地址的创建和修改删除&#xff0c;还有主页界面和商品界面

Scikit-LLM:一款大模型与 scikit-learn 完美结合的工具!

Scikit-LLM 是文本分析领域的一项重大变革&#xff0c;它将像 ChatGPT 这样强大的语言模型与 scikit-learn 相结合&#xff0c;提供了一套无与伦比的工具包&#xff0c;用于理解和分析文本。 有了 scikit-LLM&#xff0c;你可以发现各种类型的文本数据中的隐藏模式、情感和上下…

生成式AI - Knowledge Graph Prompting:一种基于大模型的多文档问答方法

大型语言模型&#xff08;LLM&#xff09;已经彻底改变了自然语言处理&#xff08;NLP&#xff09;任务。它们改变了我们与文本数据交互和处理的方式。这些强大的AI模型&#xff0c;如OpenAI的GPT-4&#xff0c;改变了理解、生成人类类似文本的方式&#xff0c;导致各种行业出现…

Spring-ProxyFactory

ProxyFactory选择cglib或jdk动态代理原理 ProxyFactory在生成代理对象之前需要决定是使用JDK动态代理还是CGLIB技术&#xff1a; public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {Overridepublic AopProxy createAopProxy(AdvisedSupport co…

cocosCreator 之 Bundle使用

版本&#xff1a; v3.4.0 语言&#xff1a; TypeScript 环境&#xff1a; Mac Bundle简介 全名 Asset Bundle(简称AB包)&#xff0c;自cocosCreator v2.4开始支持&#xff0c;用于作为资源模块化工具。 允许开发者根据项目需求将贴图、脚本、场景等资源划分在 Bundle 中&am…

从0到0.01入门React | 010.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

实验一 Anaconda安装和使用(Python程序设计实验报告)

实验一 Anaconda安装和使用 一、实验环境 Python集成开发环境IDLE/Anaconda 二、实验目的 1&#xff0e;掌握Windows下Anaconda的安装和配置。 2. 掌握Windows下Anaconda的简单使用&#xff0c;包括IDLE、Jupyter Notebook、Spyder工具的使用。 3. 掌握使用pip管理Python扩展库…

分类预测 | Matlab实现PSO-BiLSTM粒子群算法优化双向长短期记忆神经网络的数据多输入分类预测

分类预测 | Matlab实现PSO-BiLSTM粒子群算法优化双向长短期记忆神经网络的数据多输入分类预测 目录 分类预测 | Matlab实现PSO-BiLSTM粒子群算法优化双向长短期记忆神经网络的数据多输入分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现PSO-BiLSTM粒子…