下面我先简单介绍一下AOP的基础知识和使用方法,然后直接拆源码。不用BB,放在文章目录下。一、基础知识1.1什么是AOP?AOP的全称是“AspectOrientedProgramming”,即面向方面的编程。在AOP的思想中,将外围功能(如性能统计、日志、事务管理等)定义为切面,核心功能和切面功能独立开发,再将核心功能和切面功能“编织”起来"在一起,这就是所谓的AOP。AOP可以将与业务无关但被业务模块共同调用的逻辑封装起来,减少系统中代码的重复,降低模块之间的耦合度,有利于以后的扩展性和可维护性。1.2AOP的基本概念连接点(Joinpoint):可以拦截的地方,SpringAOP是基于动态代理的,所以是通过方法来拦截的,每个成员方法都可以称为一个连接点;每个方法都可以称为一个连接点。当我们具体定位某个方法时,它就成为一个切点;增强/通知(Advice):表示在切点上增加的一段逻辑代码,定位连接点的位置信息。简单地说,它定义了它做什么,在哪里做;编织:为目标类的特定连接点添加增强/通知的过程;Introduction/介绍:允许我们对现有类添加一个新的方法或属性是一种特殊的增强;切面(Aspect):切面由切点和增强/通知组成,既包括横切逻辑的定义,也包括连接点的定义。上面的解释比较官方,下面用“方言”再解释一遍。切入点:切入哪些类和方法(哪里);建议(Advice):方法执行时做什么(when:方法之前/之后/前后)(what:增强功能);Aspect(切面):aspect=entrypoint+notification,通俗点说是when、在哪里以及做什么来增强;编织(Weaving):为一个对象添加一个切面并创建一个代理对象的过程。要做。5种通知的分类:BeforeAdvice:在调用目标方法之前调用通知函数;AfterAdvice:调用目标方法后调用通知函数;after-returning:目标方法执行成功后调用通知函数;After-throwing:目标方法抛出异常后调用通知函数;环绕通知(Around):包裹整个目标方法,在被调用前和调用后分别调用通知函数。1.3简单AOP实例新建楼仔类:@Data@ServicepublicclassLouzai{publicvoideveryDay(){System.out.println("sleep");}}添加LouzaiAspect切面:@Aspect@ComponentpublicclassLouzaiAspect{@Pointcut("execution(*com.java.Louzai.everyDay())")privatevoidmyPointCut(){}@Before("myPointCut()")publicvoidmyBefore(){System.out.println("膳食");}@AfterReturning(value="myPointCut()")publicvoidmyAfterReturning(){System.out.println("打豆豆...");}}applicationContext.xml添加:程序入口:publicclassMyTest{publicstaticvoidmain(String[]args){ApplicationContextcontext=newClassPathXmlApplicationContext("classpath:applicationContext.xml");Louzailozai=(Louzai)context.getBean("louzai");louzai.everyDay();}}输出:吃饭,睡觉,打豆豆...这个例子很简单,“睡觉”加了前后通知,但是Spring内部是如何工作的1.4SpringAOP工作流为了让大家更容易理解后面的源码,我先整体介绍一下源码的执行过程,让大家有个整体的了解,不然很容易被绕过,整个SpringAOP源码,其实分为3个部分,我们结合上面的例子给大家讲解一下。第一部分是预处理。在创建楼仔Bean的预处理中,我们会遍历程序所有的切面信息,然后保存切面信息。缓存中,例如示例中LouzaiAspect的所有aspect信息。第二块是后处理。在创建楼仔Bean的后处理器中,我们会在里面做两件事:获取楼仔的切面方法:首先,会从缓存中获取楼仔的所有切面信息,匹配楼仔的所有方法,以及然后找到楼载所有需要AOP的方法。创建AOP代理对象:结合楼仔需要执行AOP的方法,选择Cglib或JDK,创建AOP代理对象。三块是切面的实现,通过“责任链+递归”来执行切面。2.源码解读注意:Spring版本是5.2.15.RELEASE,否则和我的代码不一样!!!除了原理部分,以上知识并不难,下面是我们的重头戏,带你跟着楼子走一遍代码流程。2.1代码入口这里需要多跑几次,跳过前面的beanName,只看louzai。输入doGetBean()进入创建Bean的逻辑。2.2预处理主要是遍历切面,放入缓存。这就是重点!敲黑板!!!我们将首先遍历所有类;判断是否是切面,只有切面才会进入后面的逻辑;获取各个aspect的aspect列表;将aspect的aspect列表保存到缓存advisorsCache中。至此,获取aspect信息的过程就结束了,因为后续获取aspect数据都是从缓存advisorsCache中获取的。让我们仔细看看上面的过程。2.2.1判断是否是第二步的切面图,逻辑如下:2.2.2获取切面列表,进入getAdvice()生成切面信息。2.3后处理主要是从缓存中获取切面,匹配楼仔的方法,创建AOP代理对象。输入doCreateBean()并遵循以下逻辑。这就是重点!敲黑板!!!首先获取louzai类的所有aspect列表;创建一个AOP代理对象。2.3.1获取aspect下面我们进入第一步,看看如何获??取louzai的aspect列表。进入buildAspectJAdvisors(),这个方法给人的印象应该是之前将aspect信息放入缓存advisorsCache中,现在这里是获取缓存。回到findEligibleAdvisors(),从缓存中获取到所有的aspect信息后继续执行。2.3.2创建代理对象有了楼仔的切面列表,后面就可以开始创建AOP代理对象了。这就是重点!敲黑板!!!创建AOP代理对象有2种方式,我们选择Cglib来创建。我们回到创建代理对象的入口,看一下创建的代理对象。2.4切面执行通过“责任链+递归”来执行切面和方法。前方高能!这段逻辑很复杂!!!以下是“执行层面”的核心逻辑,简单说一下设计思路:设计思路:采用递归+责任链的模式;递归:重复执行CglibMethodInvocation的proceed();退出递归条件:interceptorsAndDynamicMethodMatchers数组中的对象,全部Execution完成;责任链:例子中的责任链是一个长度为3的数组,每次取其中一个数组对象,然后执行该对象的invoke()。因为我们的数组中只有3个对象,所以我们只会递归3次。让我们看看如何递归这3次以及责任链是如何执行的。设计非常巧妙!2.4.1第一个递归数组的第一个对象是ExposeInvocationInterceptor,执行invoke(),注意入参是CglibMethodInvocation。里面什么都不做,继续执行CglibMethodInvocation的process()。2.4.2第二个递归数组的第二个对象是MethodBeforeAdviceInterceptor,执行invoke()。2.4.3第三个递归数组的第二个对象为AfterReturningAdviceInterceptor,执行invoke()。上面的逻辑执行完后,就会退出递归。再来看invokeJoinpoint()的执行逻辑,其实就是main方法的执行。回到第三次递归的入口,继续执行以下几个方面。切面执行逻辑前面已经演示过了,直接看执行方法。后面会逐一退出递归,整个过程结束。2.4.4设计思路这段代码我研究了很久,因为这不是一个纯粹的责任链模型。在纯责任链模式下,对象内部有自己的下一个对象。当前对象的方法执行结束后,会开始执行下一个对象,直到执行完最后一个next对象,或者中间因为某些情况导致执行中断,责任链退出。在这里,CglibMethodInvocation对象中没有下一个对象。整个过程由一个长度为3的interceptorsAndDynamicMethodMatchers数组控制,依次执行数组中的对象。直到最后一个对象执行完毕,责任链才会退出。这个也属于职责链,只是实现方式不同,后面会详细分析,下面会讲到这些类之间的关系。我们的主要对象是CglibMethodInvocation,它继承自ReflectiveMethodInvocation,而process()的核心逻辑其实就在ReflectiveMethodInvocation中。ReflectiveMethodInvocation中的process()控制着整个责任链的执行。ReflectiveMethodInvocation中的process()方法有一个长度为3的interceptorsAndDynamicMethodMatchers数组,里面存放了3个对象,分别是ExposeInvocationInterceptor、MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor。注意!!!这三个对象都继承了MethodInterceptor接口。然后每次执行invoke()时,都会在里面执行CglibMethodInvocation的process()。你困惑吗?别着急,我会再为你解决的。对象与方法的关系:接口继承:数组中的三个对象继承了MethodInterceptor接口,并在里面实现了invoke()方法;类继承:我们的主要对象CglibMethodInvocation继承自ReflectiveMethodInvocation,并复用了它的process()方法;两者结合(策略模式):invoke()的入参是CglibMethodInvocation。当执行invoke()时,CglibMethodInvocation.process()将在内部执行。这其实是一种策略模式。可能有同学会说invoke()的入参是MethodInvocation,没错!但是CglibMethodInvocation也继承了MethodInvocation,不信你去看看。执行逻辑:程序入口:CglibMethodInvocation的process()方法;链式执行(责任链派生模式):process()中有一个包含3个对象的数组,依次执行每个对象的invoke()方法。递归(逻辑回退):invoke()方法会执行切面逻辑,同时也会执行CglibMethodInvocation的process()方法,让逻辑再次进入process()。递归退出:当number中的3个对象都执行完,流程结束。所以这里的巧妙设计是因为纯责任链模型,里面的下一个对象需要保证里面的对象类型完全一致。但是数组中的三个对象都没有next成员对象,不能直接使用责任链模式,那怎么办呢?只是单独创建了一个CglibMethodInvocation.process(),通过去无限递归process()实现了这个责任链的逻辑。这就是为什么我们要看源码,学习其中优秀的设计思想!3.总结让我们来看看下一节。文章首先介绍了什么是AOP,以及AOP的原理和例子。然后分析AOP的源码,分为3部分:将所有的aspect保存在缓存中;取出缓存中的切面列表,匹配楼仔对象的所有方法,得到属于楼仔的切面列表;创建AOP代理对象;通过“责任链+递归”执行切面逻辑。最难的不是图像抠图,而是“裁剪执行”的设计思路。虽然过程可以走一遍,但是要将整个设计思路总结出来,解释清楚,让大家看得懂,还是非常困难的。