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

整理发出(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. 口试C#概念

    C#概念 装箱拆箱: 值类型与引用类型:值类型:System.ValueType(继承自System.Object)引用类型:System.Object 反射:反射提供一种编程方式,让程序员可以在程序 ...

  2. 利用jsonp调用外部ip地址池

    <html lang="en"> <head> <meta charset="UTF-8"> <title>Do ...

  3. (PMP)解题技巧和典型题目分析(0903-2班)

    1.计算题 ,5 2.概念题,少 3.情景题,很多 C B C D ------------------------------------------------------------------ ...

  4. 在JAVA中对于类,对象,继承,多态的看法

    这是我第一次学高级语言.很抱歉选择了JAVA,目标是开发一款可以上线的APP. 类:是建立对象的一个模板,就是系列产品中的基础款图纸.只是图纸而已.不是产品. 对象:是一个可以操作的对象.新建一个对象 ...

  5. Nexus安装、使用说明、问题总结

    Nexus安装.使用说明.问题总结 1 . 私服简介 私服是架设在局域网的一种特殊的远程仓库,目的是代理远程仓库及部署第三方构件.有了私服之后,当 Maven 需要下载构件时,直接请求私服,私服上存在 ...

  6. Greenplum 日常维护手册 (汇总、点评、备查)

    1. 数据库启动:gpstart常用可参数: -a : 直接启动,不提示终端用户输入确认-m:只启动master 实例,主要在故障处理时使用2. 数据库停止:gpstop:常用可参数:-a:直接停止, ...

  7. 浅谈Spring中的IOC容器

    一.IOC.DI基本概念 IoC(Inversion of Control)控制反转指的是把对象的创建,初始化,销毁等工作交给容器来做.由容器控制对象的生命周期(传统方式中是由我们程序员主动创建对象. ...

  8. Ubuntu修改系统时间

    在新版的ubuntu中,使用timedatectl 替换了ntpdate来进行时间管理. 1.查看当前时间状态 查看当前时间状态 timedatectl status : res@ubuntu:~$ ...

  9. Android从入门到进阶——布局

    一.组件 1.UI组件 (Android.view.View的子类或者间接子类) 2.容器组件(Android.view.ViewGroup子类或者间接子类) 二.UI组件:TextView,Spin ...

  10. Oracle去除重复(某一列的值重复),取最新(日期字段最新)的一条数据

    转自 : http://blog.csdn.net/nux_123/article/details/45037719 问题:在项目中有一张设备检测信息表DEVICE_INFO_TBL, 每个设备每天都 ...