Spring源码系列-Spring AOP

news2024/12/23 9:20:35

目录

AOP的用法

注解用法

早期的基于接口的用法

递归实现的责任链模式

简单实现

责任链模式就两个关键点

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

Advisor

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

注解形式的Advisor

解决需要创建多个FactoryBean

纯注解的AOP实现原理

AOP源码解析

AOP入口

@EnableAspectJProxy

AOP中的三大BeanPostProcessor

Spring Aop的三种实现方式

解析切面类

AnnotationAwareAspectJAutoProxxyCreator

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

cglib动态代理和jdk动态代理

创建动态代理

调用代理方法


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



 

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

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

相关文章

pta 6翻了 Python3

“666”是一种网络用语&#xff0c;大概是表示某人很厉害、我们很佩服的意思。最近又衍生出另一个数字“9”&#xff0c;意思是“6翻了”&#xff0c;实在太厉害的意思。如果你以为这就是厉害的最高境界&#xff0c;那就错啦 —— 目前的最高境界是数字“27”&#xff0c;因为这…

camera tuning 常识

#灵感# 把收集的乱东西&#xff0c;归在一起。字体颜色标浅色的&#xff0c;是扩展内容&#xff0c;为了先简单了解&#xff0c;并不做很细致的阐述。 &#xff08;1&#xff09;模组 集成封装好的模组&#xff0c;一般由三个部分组成&#xff1a;镜头&#xff08;lens&#…

如何利用反欺诈(羊毛盾)API提升电商平台交易安全性?

前言 近年来&#xff0c;随着网络交易的普及和互联网金融的快速发展&#xff0c;各类网络欺诈事件层出不穷。面对这些问题&#xff0c;电商平台必须采取有效的措施来保障交易安全。而反欺诈&#xff08;羊毛盾&#xff09;API正是一种高效、智能的解决方案&#xff0c;能够帮助…

LeetCode(10)跳跃游戏 II【数组/字符串】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 45. 跳跃游戏 II 1.题目 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nu…

XML Web 服务 Eclipse实现中的sun-jaxws.xml文件

说明 在sun-jaxws.xml文件&#xff0c;可以配置endpoint、handler-chain等内容。在这个文件中配置的内容会覆盖在Java代码中使用注解属性配置的的内容。 这个文件根据自己的项目内容修改完成以后&#xff0c;作为web应用的一部分部署到web容器中&#xff08;放到web应用的WEB…

高效批量剪辑、处理和添加水印,用视频批量剪辑高手轻松搞定!

您是否曾经在处理大量视频时&#xff0c;因为剪辑、处理和添加水印等问题而感到烦恼&#xff1f;是否因为这些问题而大大降低了您的工作效率&#xff1f;现在&#xff0c;我们为您推荐一款全新的视频批量剪辑工具——视频批量剪辑高手&#xff0c;让您的工作效率瞬间翻倍&#…

听10秒语音就能判断糖尿病,这个AI大模型太硬核了!

加拿大的Klick科研人员在顶级健康杂志《梅奥诊所文集&#xff1a;数字健康》上发布了一个AI大模型&#xff0c;只需要听一段6—10秒的语音&#xff0c;就能诊断是否患有2型糖尿病&#xff08;T2DM&#xff09;。 目前糖尿病的主要检测方式依赖于血糖测量&#xff0c;但这种方法…

【06】VirtualService高级流量功能

5.3 weight 部署demoapp v10和v11版本 --- apiVersion: apps/v1 kind: Deployment metadata:labels:app: demoappv10version: v1.0name: demoappv10 spec:progressDeadlineSeconds: 600replicas: 3selector:matchLabels:app: demoappversion: v1.0template:metadata:labels:app…

基于token的多平台身份认证架构设计

1 概述 在存在账号体系的信息系统中&#xff0c;对身份的鉴定是非常重要的事情。 随着移动互联网时代到来&#xff0c;客户端的类型越来越多&#xff0c; 逐渐出现了 一个服务器&#xff0c;N个客户端的格局 。 不同的客户端产生了不同的用户使用场景&#xff0c;这些场景&…

Python实现cpu密集型多线程

问题&#xff1a; 想通过以下代码实现多线程 class ThreadTest(threading.Thread):def __init__(self, num, *args, **kwargs):super(ThreadTest, self).__init__(*args, **kwargs)self.num numprint(num)def run(self):print(self.num)print(time.time())if __name__ &quo…

Git-工作流

前言 一、工作流概述二、Git flow1.主要流程2.优缺点3.适用场景 三、Github flow1.主要流程2.优缺点3.适用场景 四、Gitlab flow1.主要流程2.优缺点3.适用场景 总结参考 一、工作流概述 开发人员通过Git可以记录和追踪代码的变化&#xff0c;包括添加、删除和修改文件。如果是…

贪吃蛇和俄罗斯方块

贪吃蛇 一、创建新项目 创建一个新的项目&#xff0c;并命名。 创建一个名为images的文件夹用来存放游戏相关图片。 然后再在项目的src文件下创建一个com.xxx.view的包用来存放所有的图形界面类&#xff0c; 创建一个com.xxx.controller的包用来存放启动的入口类(控制类) …

C语言精选练习题:(7)计算最大值和最小值的差

每日一言 欲把西湖比西子&#xff0c;淡妆浓抹总相宜。 --饮湖上初晴后雨二首其二 题目 输入10个整数&#xff0c;找出其中的最大和最小值&#xff0c;计算两者的差&#xff0c;并打印出来 解题思路 创建一个数组用循环将10个整数存到数组中用打擂台的方式求出最大和最小值打…

浅谈二叉树

✏️✏️✏️今天给大家分享一下二叉树的基本概念以及性质、二叉树的自定义实现&#xff0c;二叉树的遍历等。 清风的CSDN博客 &#x1f61b;&#x1f61b;&#x1f61b;希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&…

讲座录播 | 邹磊教授:图数据库的概念和应用

2023年10月16日 由中国计算机学会主办的 “CCF Talk”直播间 进行了题目为 术语解读:“图计算”的内涵与应用 主题直播活动 讲座吸引7708人观看 图作为一种灵活表达复杂关联关系的数据结构&#xff0c;目前已广泛地应用于社会治理、医疗健康、电网分析、计算材料、计算育…

嵌入式软件工程师面试题——2025校招社招通用(十三)

说明&#xff1a; 面试题来源于网络书籍&#xff0c;公司题目以及博主原创或修改&#xff08;题目大部分来源于各种公司&#xff09;&#xff1b;文中很多题目&#xff0c;或许大家直接编译器写完&#xff0c;1分钟就出结果了。但在这里博主希望每一个题目&#xff0c;大家都要…

Linux下MSSQL (SQL Server)数据库无法启动故障处理

有同事反馈一套CentOS7下的mssql server2017无法启动需要我帮忙看看&#xff0c;启动报错情况如下 检查日志并没有更新日志信息 乍一看mssql-server服务有问题&#xff0c;检查mssql也确实没有进程 既然服务有问题&#xff0c;那么我们用一种方式直接手工后台启动mssql引擎来…

有什么价格实惠的猫罐头?2023良心性价比的猫罐头推荐!

选购猫罐头至关重要&#xff0c;好的猫罐头不仅营养丰富&#xff0c;水分充足&#xff0c;适口性佳&#xff0c;还能易于消化吸收。然而&#xff0c;若选择不当&#xff0c;可能不仅无法达到预期效果&#xff0c;甚至可能产生负面影响。 作为一个从事宠物行业7年的宠物店店长&…

Python语言的十大特性。

文章目录 前言一、Python二、Python 编程语言的特性三、开源四、Python 中的 GUI 编程支持五、Python 支持高级语言六、可扩展性七、可移植性八、大型标准库九、解释性语言十、面向对象程序设计语言十一、表达力十二、常见问题总结Python技术资源分享1、Python所有方向的学习路…

【Mybatis小白从0到90%精讲】17:Mybatis Mapper XML必须同包同名吗?mappers配置的4种方式详解

文章目录 前言一、package方式二、resource方式三、url方式四、class方式五、源码求证前言 对于Mybatis Mapper,通常都是 Java接口与Mapper.xml 同包同名,那么是否必须同包同名? 实际上并不是必须,对于Mapper的配置,是在全局配置文件 mybatis-config.xml 中 通过mappers…