Spring AOP原理分析

AOP是Spring Core中几大重要能力之一,我们可以使用AOP实现很多功能,比如我们常用的日志处理与Spring中的声明式事务。

AOP的几个重要概念

Aspect:切面,在Spring中意为所有通知方法所在的类

Join point:连接点,程序执行中的一点,在Spring中只表示方法执行(Spring只支持方法级别的拦截)

Advice:通知,在特定连接点上采取的操作,Spring将通知抽象为拦截器,并围绕连接点维护拦截器链。共有5种类型,before(切点之前执行),around(环绕执行,即切点前后执行),After returning(切点正常执行完返回后执行),After throwing(切点抛出异常后执行),after(切点之后执行,不管是异常或正常结束),AOP拦截器链则为以上五种通知组成。我们可以在通知方法中获得我们需要的参数(返回值,异常信息,代理对象等)

Pointcut:切点,与通知一起出现,使用专门的切点表达式决定在何处执行通知方法。

Introduction:引入,为类添加新的方法或字段。

Target object:被代理的对象

AOP proxy:AOP代理对象,由JDK动态代理或CGLIB代理生成

Weaving:织入,将通知等织入代理类。Spring AOP是动态织入(运行时织入),AspectJ则是静态织入(编译时织入)

几个注意点

关于Spring AOP的具体使用这里不做介绍,具体见文档,这里说几个在使用代理时需要注意的地方。

由于Spring AOP框架基于代理的特性,目标对象内的调用根据定义不会被拦截。自调用即类似this.bar()或this.foo()这样的调用,即使在bar方法上有通知方法通知也不会执行。对于JDK代理,只能拦截代理上的公共接口方法调用。使用CGLIB,可以拦截代理上的公共和受保护方法调用(Cglib基于子父类实现代理,而私有方法不会被子类继承)。当多个通知都想在同一个连接点上运行时,他们将按照优先级顺序执行,优先级顺序可以使用Order接口来定义。

总结一句:自调用通知方法不执行,私有方法通知不执行。

原理

接下来从源码角度分析下Spring AOP的实现原理。

在Spring中我们使用@EnableAspectJautoProxy开启AOP功能,我们以此为入口。(其他的Enable注解分析原理都是一样的,比如EnableAsync等)。

@EnableAspectJautoProxy

它使用@Import注解导入了AspectJAutoProxyRegistrar类,该类实现了ImportBeanDefinitionRegistrar接口,用于向Spring中注册类。

AspectJAutoProxyRegistrar

在registerBeanDefinitions方法的第一行注册了AOP需要的相关bean,方法中的下面部分是取EnableAspectJAutoProxy注解的信息,根据参数值做相应的处理,这里主要关注方法的首行代码,进入registerAspectJAnnotationAutoProxyCreatorIfNecessary方法。

发现最终注册了AnnotationAwareAspectJAutoProxyCreator。该类是实现AOP的基础,我们对该类进行分析,首先来看继承结构

BeanFactoryAware接口主要用于设置BeanFactory,这里我们主要关注InstantiationAwareBeanPostProcessor与BeanPostProcessor接口,实现这两个接口意味着AnnotationAwareAspectJAutoProxyCreator是一个Spring的后置处理器,后置处理器会在bean的创建过程中起作用,关于后置处理器不熟悉的同学可以去看这篇文章Spring之IOC容器初始化

InstantiationAwareBeanPostProcessor

InstantiationAwareBeanPostProcessor有一个before与after接口,由接口名可知两个方法分别在bean实例化前后调用,关于Spring中bean的实例化过程不清楚的可以看Spring Bean的实例化分析。我们在子类中找到他们的实现(after方法由于没有特别的处理这里就省略了)

before的实现

首先从缓存中获取,然后调用this.isInfrastructureClass(beanClass)判断创建的类是否为Advice、Pointcut等相关类,若是则放入adviseBean集合并返回null,正常的bean经过该方法会返回null,这里主要是用来处理我们的切面类。

isInfrastructureClass

bean创建完成后接下来就是另一个接口BeanPostprocess(实例化,调用构造函数)开始起作用了。

BeanPostprocess

他会在InstantiationAwareBeanPostProcessor(初始化,即BeanDefination的初始化)接口方法执行完之后调用,查看其实现(before方法由于没有特别的处理这里就省略了)

最终会调用wrapIfNecessary方法判断该bean是否需要增强。进入方法

正常bean的创建会进入到isInfrastructureClass这个分支,isInfrastructureClass这个方法就是之前分析的判断是否是Aspect等注解的类,如果不是则调用getAdvicesAndAdvisorsForBean方法获取到符合该bean的通知方法(即相应的Advisor)。

最终调用createProxy创建代理对象。

createProxy
接上图右侧

最终进入代理工厂创建代理对象的方法,根据是否实现接口自动选择创建JDK动态代理(基于接口)或者是Cglib代理(基于子父类)。到这里切面以及要被代理的类就都创建完成了,接下来就是如何运行通知方法了。

执行流程

我们这里假设上一步创建的对象为Cglib对象,了解过Cglib代理的同学都知道实现代理要实现MethodInterceptor接口,在里面的intercept方法中进行方法的拦截。我们找到代理类的intercept方法

首先调用getInterceptorsAndDynamicInterceptionAdvice方法获取所有通知方法的Advisor拦截器链,chain不为空会依次调用对应的Advisor拦截器的proceed方法进行代理调用,在此会按照通知的顺序执行原方法与通知方法。

大体的执行流程就分析完了,有时间的同学最好简单写个demo然后跟着一步步debug,这样能够更清晰的了解流程。

最后总结一下,容器初始化时将切面等信息放入通知集合中,正常bean在创建时会判断该bean是否需要被增强,若需要增强,创建相应的代理对象。在执行时,代理对象执行相应的invoke方法,在方法中获取到通知集合并抽象成拦截器链,使用拦截器模式按照顺序执行相应的方法。

附两张流程图:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容