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

整理发出(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. 关于下载calipso数据集以及用python将其读到记事本小结

    今天终于把老板交代的事情忙完了,对于我这位计算机语言的小白来说,其中的艰辛不用说,一把辛酸泪啊!在有计算机语言经验的老手而言,我这些问题似乎也不能算是问题,但我却卡了很久,对此,想把自己所遇到的困难和 ...

  2. Libgdx slg游戏进程记录

    2月16日缩放居中,stage确定点击坐标,背景处理为actor 2月17日地图多次点击 2月19日stage确定点击位置(贝塞尔曲线六边形) 2月24日格式长度,读取xml属性解析btl保存 3月1 ...

  3. java根据图片创建日期,或最后修改日期重命名

    import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import jav ...

  4. web应用/路由控制/视图函数/单表多表操作

    一. 1.wen应用:BS架构的应用程序,B是浏览器,S:server(实现了wsgi协议)+ application https://www.cnblogs.com/liuqingzheng/art ...

  5. Apache Tomcat Eclipse Integration

    An Illustrated Quick Start Guide Apache Tomcat makes hosting your applications easy. The Eclipse IDE ...

  6. eclipse设置新建jsp文件默认字符编码为utf-8

    在使用Eclipse开发中,编码默认是ISO-8859-1,不支持中文.这样我们每次新建文件都要手动修改编码,非常麻烦.其实我们可以设置文件默认编码,今后再新建文件时就不用修改编码了. 1.打开Ecl ...

  7. redis设置远程通过密码进行连接

    个人配置:服务器+本地鸡+win 文件概况.↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 首先修改第一个redis.windows.conf 56行左右,将bind 127.0.0.1注释掉(行前面加上#    ...

  8. 项目设计day1

    项目内容:一个实时监控斗鱼TV某个主播弹幕的设计 通过python爬虫获取当前弹幕,通过flume采集数据,接下来数据分为线上和线下两种方案: 线上:实时分析,分为两种方案:(1) flume+kaf ...

  9. ansible 关闭ssh首次连接时提示

    关闭ssh首次连接时提示. 修改/etc/ansible/ansible.cfg配置文件 方法一:(推荐,配置文件中存在) host_key_checking = False 方法二: ssh_arg ...

  10. ASP.NET Core 微服务初探[2]:熔断降级之Polly

    当我们从单体架构迁移到微服务模式时,其中一个比较大的变化就是模块(业务,服务等)间的调用方式.在以前,一个业务流程的执行在一个进程中就完成了,但是在微服务模式下可能会分散到2到10个,甚至更多的机器( ...