此博文的编写,源于前段时间的惨痛面试经历。刚好近几天尘埃落定、手头事少,遂总结一二,与各位道友分享,欢迎吐槽指正。今年年初的这段面试经历,已于之前的博文中

整理发出(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切面实现原理以及多个切面切同一个地方时的优先级讲解的更多相关文章

  1. Spring AOP基于配置文件的面向方法的切面

    Spring AOP基于配置文件的面向方法的切面 Spring AOP根据执行的时间点可以分为around.before和after几种方式. around为方法前后均执行 before为方法前执行 ...

  2. spring aop在mvc的controller中加入切面无效

    spring aop在mvc的controller中加入切面无效 因为MVC的controller,aop默认使用jdk代理.要使用cglib代理. 在spring-mybatis.xml配置文件中加 ...

  3. Spring框架IOC和AOP的实现原理(概念)

    IoC(Inversion of Control) (1). IoC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控.控制权由应用代码中 ...

  4. Spring框架IOC和AOP的实现原理

    IoC(Inversion of Control) (1). IoC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控.控制权由应用代码中 ...

  5. AOP基本概念、AOP底层实现原理、AOP经典应用【事务管理、异常日志处理、方法审计】

    1 什么是AOP AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件 ...

  6. 关于spring,IOC和AOP的解析原理和举例

    引用自:https://blog.csdn.net/paincupid/article/details/43152397 IOC:就是DAO接口的实现不再是业务逻辑层调用工厂类去获取,而是通过容器(比 ...

  7. Spring AOP 的实现 原理

    反射实现 AOP 动态代理模式实例说明(Spring AOP 的实现 原理)   比如说,我们现在要开发的一个应用里面有很多的业务方法,但是,我们现在要对这个方法的执行做全面监控,或部分监控.也许我们 ...

  8. 浅析Spring中AOP的实现原理——动态代理

    一.前言   最近在复习Spring的相关内容,刚刚大致研究了一下Spring中,AOP的实现原理.这篇博客就来简单地聊一聊Spring的AOP是如何实现的,并通过一个简单的测试用例来验证一下.废话不 ...

  9. Spring技术内幕:Spring AOP的实现原理(一)

    一.SpringAOP的概述 1.AOP概念 AOP是Aspect-Oriented Programming(面向切面编程)的简称.维基百科的解释例如以下: Aspect是一种新的模块化机制,用来描写 ...

随机推荐

  1. 对TIMIT数据进行格式转换(SPHERE2WAV(RIFF))

    首先,转换sph2pipe工具所在文件夹(此工具为LDC所提供的SPHERE音频文件转换工具) cd '/home/dream/Research/kaldi-master/tools/sph2pipe ...

  2. 浅谈JS面向对象

    浅谈JS面向对象 一 .什么是面向过程 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了.注重代码的过程部分. 二.什么是面向对象 最先出现在管理学 ...

  3. UTF-8的BOM含义

    BOM的介绍 在github上写md文件的时候,发现生成自己blog时,报出一个错误是让使用UTF-8编码,然后在Notepad++上把文件转成UTF-8时,发现菜单中有"UTF-8无BOM ...

  4. centos 下Python独立虚拟环境创建

    virtualenv Python有着庞大的开源社区的支持,很自然就产生这么一个问题:第三方包参差不齐,如果我们想在服务器测试,或者升级某个包,就会导致生产环境产生杂乱,多余的第三方依赖包. virt ...

  5. Mongo学习笔记

    安装和开始 下载 MongoDB 参考:+MongoDB安装配置(Windows) +Mongo手册

  6. mac 电脑下svn

    mac点下使用的提交代码的方式是:eclipse + svn 的插件实现,总之中间遇到了很多的问题,不过还是都解决了. 前提是你已经安装了jdk. 安装的过程不再赘述直接上链接http://www.c ...

  7. 【收藏】JS获取鼠标的X,Y坐标位置

    JS的方法: <html> <head> <meta http-equiv="Content-Type" content="text/htm ...

  8. 个人对于angularjs依赖注入的理解

    依赖注入(Dependency Injection,DI),作者认为本文中所有名词性的"依赖" 都可以理解为 "需要使用的资源". 对象或者函数只有以下3种获取 ...

  9. linux sshd 登录不需要密码

    ssh 安全外壳协议 协议22 linux 默认放行了 22 号接口 ssh 默认安装 自行安装 应该是 openssh-server ssh命令是 openssh ssh-keygen 生成密钥 生 ...

  10. gogs 安装

    docker 安装gogs 准备工作 安装一个mysql数据库,创建一个数据库 gogs,字符集为utf-8 查找gogs 镜像 docker search gogs 拉取镜像到本地 docker p ...