AOP切面实现原理以及多个切面切同一个地方时的优先级讲解
此博文的编写,源于前段时间的惨痛面试经历。刚好近几天尘埃落定、手头事少,遂总结一二,与各位道友分享,欢迎吐槽指正。今年年初的这段面试经历,已于之前的博文中
整理发出(https://www.cnblogs.com/zzq6032010/p/10492109.html)。不会不丢人,但如果不会还不去整理总结、不去学习,这才是最丢人的!闲话少叙,下面开始正文。
注:本文是基于《Spring源码深度解析》(郝佳编著)一书梳理归纳而来,如果大家能结合Spring源码看,相信会了解更深刻。
零、概述
Spring的AOP实现原理是什么? 当有多个切面的切点切到同一个方法时,AOP是如何处理多个切点的调用顺序的?对于AOP的实现原理,想必大家都有过了解。 通过JDK或者
CGLIB动态代理创建指定方法的代理,执行方法时则根据切点匹配到对应的增强,执行之。但如果对源码有过了解,就会发现实际实现的过程复杂的多,远没有描述中的那么简单。
照例先粗略的罗列一下总流程:当多个切点切到同一个方法时,源码实现流程为:Spring容器启动时先注册AnnotationAwareAspectJAutoProxyCreator类的BeanDefinition(继承
自后处理器BeanPostProcessor),当程序开始调用实际的切面方法要生成bean实例时,会调用其postProcessAfterInitialization方法(对于BeanPostProcessor等后处理器的作用原
理详见另一篇博文 https://www.cnblogs.com/zzq6032010/p/10466378.html ),此方法创建代理替换了Bean实例 。在代理中包含了此方法的所有拦截器,当调用方法时,在代理的
invoke方法中,将拦截器封装进ReflectiveMethodInvocation(如果是CGLIB代理则是封装进CglibMethodInvocation),逐个调用其proceed方法实现增强方法的调用。
下面会按照以上流程的顺序,从注册AnnotationAwareAspectJAutoProxyCreator、创建AOP代理、调用目标方法这三个阶段详细讲述多个切点切同一个方法时AOP整个的流程。
一、注册AnnotationAwareAspectJAutoProxyCreator
在Spring源码中全局搜索启动AOP的自定义标签aspectj-autoproxy(<aop:aspectj-autoproxy>),可以定位到注册该自定义标签解析类的类AopNamespaceHandler。对自定义标
签有过了解的道友应该知道,该解析类的parse方法是解析的核心,代码如下所示:
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
AopNamespaceUtils.registerAtAspectJAutoProxyCreatorIfNecessary(parserContext, element);
extendBeanDefinition(registry, element);
return null;
}
可知,在注册的解析类AspectJAutoProxyBeanDefinitionParser的parse方法中,关键处是调用了AopNamespaceUtils的静态方法registerAspectJAnnotationAutoProxyCreatorIfNecessary。
此方法中完成了三个功能:
1、注册beanName为org.Springframework.aop.config.internalAutoProxyCreator、class为AnnotationAwareAspectJAutoProxyCreator的BeanDefinition
2、处理proxy-target-class(如果为true则使用cglib代理)跟expose-proxy(暴露当前的aop代理类,可用AopContext.currentProxy()获取)属性(此处对这两个属性的处理,是指以
key-value的形式放入BeanDefinition的propertyValues属性中)
3、注册组件并通知
此时,如果有多个切面类,Spring容器启动时会按照你配置的方式(在XML中以Bean标签的形式配置或者在切面类上加注解的方式)将这多个切面类作为BeanDefinition加载注册到容器中,
而是否有多个切面类对于此处解析并注册AnnotationAwareAspectJAutoProxyCreator这个BeanDefinition是没有影响的。
二、创建AOP代理
第一步中注册的类AnnotationAwareAspectJAUtoProxyCreator到底有何玄机,为何要注册它?且看看这个类的继承关系图:

可知此类继承了BeanPostProcessor,那么顺藤摸瓜查看其实现的方法postProcessorAfterInitialization。由之前的博文https://www.cnblogs.com/zzq6032010/p/10466378.html 可知,
此方法的执行时机是从Spring容器中getBean时初始化完Bean对象之后。外化到程序中,即当你要在程序中通过@Autowired等注解给成员变量进行依赖注入时执行。如果此时要依赖注入
的类中有被切面切到的PointCut,那么执行完postProcessorAfterInitialization方法后依赖注入的对象就是新生成的代理对象了。
追溯postProcessorAfterInitialization方法,可知关键点有两处:
1、getAdvicesAndAdvisorsForBean方法获取到所有增强,以Object[]的形式存放;
2、createProxy方法针对增强创建代理,最终postProcessorAfterInitialization方法返回的对象就是这个创建的代理对象,而此代理对象最后就成了getBean方法获取到的对象。
增强获取:
在AbstractAdvisorAutoProxyCreator类的aspectJAdvisorsBuilder.buildAspectJAdvisors()方法中,先获取所有的beanName,然后遍历beanNames,校验每一个beanName对应的type,
如果有AspectJ的注解,则通过advisorFactory.getAdvisor(factory)方法获取此切面类下的所有增强方法(先找到有Advice类注解(如@Before、@Around等)的方法,然后给每一个切点生成
对应PointCut对象,用InstantiationModelAwarePointcutAdvisorImpl统一封装,并对不同的PoinCut使用对应的增强器初始化(如@Before对应AspectJMethodBeforeAdvice)增强器),以
List<Advisor>形式存放。其中,切面中每个增强+对应的PointCut对应一个Advisor。
然后筛选获取到的所有增强器,只取到与当前bean相关的Advisor。相关方法为findAdvisorThatCanApply,其中过滤增强分了两种,一种是引介增强IntroductionAdvisor(类级别的拦截),
一种是普通的增强。
创建代理:
createProxy方法中,首先是创建了一个ProxyFactory,并对其进行了初始化,然后才是调用此代理工厂的getProxy方法获得代理对象。如果此处有多个Advisor,则将其添加到ProxyFactory
的List<Advisor>成员变量中。下面追溯代理对象的创建过程。
先创建了AopProxyFactory,又创建了AopProxy,最后通过getProxy方法获得代理对象。此处创建AopProxy时,会根据配置项或者代理类的特性选择是JdkDynamicAopProxy还是CglibProxy。
至于增强,则封装进了此代理对象的属性AdvisedSupport advised中。
三、调用目标方法
当调用第二步中获取到代理对象后,根据代理模式,我们知道程序会走invoke方法,就是在此方法中完成了对增强的调用。下面以JdkDynamicAopProxy为例,查看其invoke方法。
有两个重要的点:
1、对于exposeProxy的处理
当代理走到invoke方法时,如果之前解析到的exposeProxy为true,则通过AopContext.setCurrentProxy(proxy)将当前代理放入这个属性中,这样,我们在代码中使用AopContext.getCurrentProxy
才能获取到当前的代理对象。
2、拦截器链(即增强)的调用
在invoke方法中,将当前方法的所有拦截器都封装进ReflectiveMethodInvocation中,调用其proceed()方法,使所有拦截器生效。不同的增强器,如@Before、@After,他们的执行顺序由他们自身
功能来控制。
总结:
AOP的实现原理如上所述。对于一个切面中多个不同Advice的执行顺序,是由对应增强器的invoke方法本身实现的,具体顺序如下所示:
目标方法正常执行:@Around前 ->@Before ->执行方法 -> @Around后 -> @After -> @AfterReturning
目标方法抛异常: @Around前 ->@Before ->方法报错 -> @After -> @AfterThrowing
对于多个切面类切同一个方法,哪个切面类中的增强器先执行?从上述AOP实现原理中可知AOP中没有规定不同切面的执行顺序,都是把切面打乱放进了List<Advisor>中,但从放入List中的顺序追溯,
可知对应的是Spring加载类后注册BeanDefinition的顺序,即Spring注册BeanDefinition的顺序。而此顺序有两个方法控制,一个是在类上加@Order(123)注解,后面的数字越小越早加载;另一个是实现
Ordered接口,重写getOrder方法,返回的值越小越早加载。好吧,追溯了一顿还是回到Spring中,哎,洗洗睡了。
AOP切面实现原理以及多个切面切同一个地方时的优先级讲解的更多相关文章
- Spring AOP基于配置文件的面向方法的切面
Spring AOP基于配置文件的面向方法的切面 Spring AOP根据执行的时间点可以分为around.before和after几种方式. around为方法前后均执行 before为方法前执行 ...
- spring aop在mvc的controller中加入切面无效
spring aop在mvc的controller中加入切面无效 因为MVC的controller,aop默认使用jdk代理.要使用cglib代理. 在spring-mybatis.xml配置文件中加 ...
- Spring框架IOC和AOP的实现原理(概念)
IoC(Inversion of Control) (1). IoC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控.控制权由应用代码中 ...
- Spring框架IOC和AOP的实现原理
IoC(Inversion of Control) (1). IoC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控.控制权由应用代码中 ...
- AOP基本概念、AOP底层实现原理、AOP经典应用【事务管理、异常日志处理、方法审计】
1 什么是AOP AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件 ...
- 关于spring,IOC和AOP的解析原理和举例
引用自:https://blog.csdn.net/paincupid/article/details/43152397 IOC:就是DAO接口的实现不再是业务逻辑层调用工厂类去获取,而是通过容器(比 ...
- Spring AOP 的实现 原理
反射实现 AOP 动态代理模式实例说明(Spring AOP 的实现 原理) 比如说,我们现在要开发的一个应用里面有很多的业务方法,但是,我们现在要对这个方法的执行做全面监控,或部分监控.也许我们 ...
- 浅析Spring中AOP的实现原理——动态代理
一.前言 最近在复习Spring的相关内容,刚刚大致研究了一下Spring中,AOP的实现原理.这篇博客就来简单地聊一聊Spring的AOP是如何实现的,并通过一个简单的测试用例来验证一下.废话不 ...
- Spring技术内幕:Spring AOP的实现原理(一)
一.SpringAOP的概述 1.AOP概念 AOP是Aspect-Oriented Programming(面向切面编程)的简称.维基百科的解释例如以下: Aspect是一种新的模块化机制,用来描写 ...
随机推荐
- hive 优化方法
https://blog.csdn.net/jiangsanfeng1111/article/details/52847044 -- 高级优化 使用各种函数hive>show functions ...
- 无法获得锁 /var/lib/dpkg/lock - open
关于 apt-get update 更新的时候出现 无法获得锁 /var/lib/dpkg/lock - open 解决 直接删除这个锁文件即可: sudo rm /var/lib/dpkg/lock ...
- Redis讲解以及测试要点
一.redis 简介 redis适合放一些频繁使用,比较热的数据,因为是放在内存中,读写速度都非常快,一般会应用在下面一些场景,排行榜.计数器.消息队列推送.好友关注.粉丝. 首先要知道mysql存储 ...
- Notes : <Hands-on ML with Sklearn & TF> Chapter 4
.caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...
- Java 异常与反射 总结
1.异常 异常,简单来说,就是一个程序执行过程中发生的不正常情况的事件.它发生在程序的运行期间,干扰了正常的指令流程.如果没有处理异常,那么出现异常之后,程序会停止运行.异常分为运行异常和非运行异常. ...
- 《高质量C++&C 编程指南》学习笔记
这本电子书是在国科大上课时候,老师在课件资源里边提供的.之所以会重视这个文件,是因为本科时候,有个老师提到过:那个学生遍的代码很整齐,看起来让人舒服,我就知道工大留不下他.因此,我就格外注意这件事,但 ...
- java实现自动生成小学四则运算——朱庭震,詹祺豪
组员:朱庭震,詹祺豪 Github地址:https://github.com/ztz1998/second/tree/master 1题目:实现一个自动生成小学四则运算题目的命令行程序. 2说明: 自 ...
- C#算法与数据结构之线性结构
线性结构是什么? 线性结构是一种数据结构,它有一对一的关系,就像一个长对,一个接一个,特点是,除第一个元素和最后一个元素外,其它元素前后只有一个元素. 简单示例1: static void Main( ...
- [转]kaldi基于GMM做分类问题
转自:http://blog.csdn.net/zjm750617105/article/details/55211992 对于每个类别的GMM有几种思路: 第一是将所有训练数据按类别分开,每类的数据 ...
- CSS3中的一些属性
1. 可匹配部分字符串 2. box-sizing属性 3. CSS3多栏布局 1.可匹配部分字符串 /*^运算符,匹配字符串首部*/ a[href^='http://website'] /*$运算符 ...