觉得不错请按下图操作,掘友们,哈哈哈!!!
一:概述以及目录
前两篇分享的 Spring 源码,【Spring源码解析-Bean ⽣命周期】,【Spring源码解析-Spring 循环依赖】,这个是源码系列的第 3 篇。
前两篇的源码解析,涉及到很多根底知识,可是源码的解读都不难,这篇⽂章刚好相反,依赖的根底知识不多,但 是源码⽐较难明。 下⾯我会简略介绍⼀下 AOP 的根底知识,以及使⽤⽅法,然后直接对源码进⾏拆解。目录如下:
二:根底知识
2.1 什么是 AOP ?
AOP 的全称是 “Aspect Oriented Programming”,即⾯向切⾯编程。
在 AOP 的思维⾥⾯,周边功用(⽐如性能计算,⽇志,事务管理等)被定义为切⾯,核⼼功用和切⾯功用分别独 ⽴进⾏开发,然后把核⼼功用和切⾯功用“织造”在⼀起,这就叫 AOP。
AOP 能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑封装起来,便于减少系统的重复代码,下降模块间的 耦合度,并有利于未来的可拓展性和可维护性。
2.2 AOP 根底概念
- 连接点(Join point):能够被拦截的地⽅,Spring AOP 是根据动态署理的,所以是⽅法拦截的,每个成员⽅ 法都能够称之为连接点;
- 切点(Poincut):每个⽅法都能够称之为连接点,咱们详细定位到某⼀个⽅法就成为切点; 增强/告诉
- (Advice):表示增加到切点的⼀段逻辑代码,并定位连接点的⽅位信息,简略来说就定义了是⼲什 么的,详细是在哪⼲;
- 织⼊(Weaving):将增强/告诉增加到⽬标类的详细连接点上的进程;
- 引⼊/引介(Introduction):允许咱们向现有的类增加新⽅法或属性,是⼀种特殊的增强;
- 切⾯(Aspect):切⾯由切点和增强/告诉组成,它既包括了横切逻辑的定义、也包括了连接点的定义。
上⾯的解说偏官⽅,下⾯⽤“⽅⾔”再给⼤家解说⼀遍。
切⼊点(Pointcut):在哪些类,哪些⽅法上切⼊(where); 告诉(Advice):在⽅法执⾏的什么机遇(when:⽅法前/⽅法后/⽅法前后)做什么(what:增强的功 能); 切⾯(Aspect):切⾯ = 切⼊点 + 告诉,浅显点便是在什么机遇,什么地⽅,做什么增强; 织⼊(Weaving):把切⾯加⼊到目标,并创立出署理目标的进程,这个由 Spring 来完结。
5 种告诉的分类:
- 前置告诉(Before Advice):在⽬标⽅法被调⽤前调⽤告诉功用;
- 后置告诉(After Advice):在⽬标⽅法被调⽤之后调⽤告诉功用;
- 返回告诉(After-returning):在⽬标⽅法成功执⾏之后调⽤告诉功用;
- 反常告诉(After-throwing):在⽬标⽅法抛出反常之后调⽤告诉功用;
- 环绕告诉(Around):把整个⽬标⽅法包裹起来,在被调⽤前和调⽤之后分别调⽤告诉功用。
2.3 AOP 简略示例
新建 Model 类:
@Data
@Service
public class Model {
public void everyDay() {
System.out.println("睡觉");
}
}
增加 ModelAspect 切⾯:
@Aspect
@Component
public class ModelAspect {
@Pointcut("execution(* com.java.Model.everyDay())")
private void myPointCut() {
}
// 前置告诉
@Before("myPointCut()")
public void myBefore() {
System.out.println("吃饭");
}
// 后置告诉
@AfterReturning(value = "myPointCut()")
public void myAfterReturning() {
System.out.println("打⾖⾖。。。");
}
}
applicationContext.xml 增加:
<!--启⽤@Autowired等注解-->
<context:annotation-config/>
<context:component-scan base-package="com" />
<aop:aspectj-autoproxy proxy-target-class="true"/>
程序⼊⼝:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context =new
ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Model model = (Model) context.getBean("model");
model.everyDay();
}
}
输出:
- 吃饭
- 睡觉
- 打⾖⾖。。。
这个示例⾮常简略,“睡觉” 加了前置和后置告诉,可是 Spring 在内部是怎么⼯作的呢?
2.4 Spring AOP ⼯作流程
为了⽅便⼤家能更好看懂后⾯的源码,我先全体介绍⼀下源码的执⾏流程,让⼤家有⼀个全体的知道,不然简单被 绕进去。
整个 Spring AOP 源码,其实分为 3 块,咱们会结合上⾯的示例,给⼤家进⾏解说。
第⼀块便是前置处理,咱们在创立 Model Bean 的前置处理中,会遍历程序一切的切⾯信息,然后将切⾯信息保存 在缓存中,⽐如示例中 ModelAspect 的一切切⾯信息。
第⼆块便是后置处理,咱们在创立 Model Bean 的后置处理器中,⾥⾯会做两件工作:
- 获取 Model 的切⾯⽅法:⾸先会从缓存中拿到一切的切⾯信息,和 Model 的一切⽅法进⾏匹配,然后找到 Louzai 一切需求进⾏ AOP 的⽅法。
- 创立 AOP 署理目标:结合 Model 需求进⾏ AOP 的⽅法,选择 Cglib 或 JDK,创立 AOP 署理目标。
第三块便是执⾏切⾯,经过“职责链 + 递归”,去执⾏切⾯。
三:源码解读
留意:Spring 的版本是 5.2.15.RELEASE,不然和我的代码不⼀样!!!
3.1 代码⼊⼝
这⾥需求多跑⼏次,把前⾯的 beanName 跳过去,只看 Model。
进⼊ doGetBean(),进⼊创立 Bean 的逻辑。
3.2 前置处理
首要便是遍历切⾯,放⼊缓存。
这⾥是要点!敲⿊板!!!
- 咱们会先遍历一切的类;
- 判断是否切⾯,只要切⾯才会进⼊后⾯逻辑;
- 获取每个 Aspect 的切⾯列表;
- 保存 Aspect 的切⾯列表到缓存 advisorsCache 中。
到这⾥,获取切⾯信息的流程就完毕了,因为后续对切⾯数据的获取,都是从缓存 advisorsCache 中拿到。
下⾯就对上⾯的流程,再深⼊解读⼀下。
3.2.1 判断是否是切⾯
上图的第 2 步,逻辑如下:
3.2.2 获取切⾯列表
进⼊到 getAdvice(),⽣成切⾯信息。
3.3 后置处理
首要便是从缓存拿切⾯,和 model 的⽅法匹配,并创立 AOP 署理目标。
进⼊ doCreateBean(),⾛下⾯逻辑。
这⾥是要点!敲⿊板!!!
- 先获取 louzai 类的一切切⾯列表;
- 创立⼀个 AOP 的署理目标。
3.3.1 获取切⾯
咱们先进⼊第⼀步,看是怎么获取 model 的切⾯列表。
进⼊ buildAspectJAdvisors(),这个⽅法应该有形象,便是前⾯将切⾯信息放⼊缓存 advisorsCache 中,现在这⾥ 便是要获取缓存。
再回到 findEligibleAdvisors(),从缓存拿到一切的切⾯信息后,持续往后执⾏。
3.3.2 创立署理目标
有了 model 的切⾯列表,后⾯就能够开端去创立 AOP 署理目标。
这⾥是要点!敲⿊板!!!
咱们再回到创立署理目标的⼊⼝,看看创立的署理目标。
3.4 切⾯执⾏
经过 “职责链 + 递归”,执⾏切⾯和⽅法。 这⾥有 2 种创立 AOP 署理目标的⽅式,咱们是选⽤ Cglib 来创立。
前⽅⾼能!这块逻辑⾮常杂乱!!!
下⾯便是“执⾏切⾯”最核⼼的逻辑,简略说⼀下规划思路:
- 规划思路:采⽤递归 + 职责链的形式;
- 递归:反复执⾏ CglibMethodInvocation 的 proceed();
- 退出递归条件:interceptorsAndDynamicMethodMatchers 数组中的目标,悉数执⾏完毕;
- 职责链:示例中的职责链,是个⻓度为 3 的数组,每次取其中⼀个数组目标,然后去执⾏目标的 invoke()。
因为咱们数组⾥⾯只要 3 个目标,所以只会递归 3 次,下⾯就看这 3 次是怎么递归,职责链是怎么执⾏的,规划得 很奇妙!
3.4.1 第⼀次递归
数组的第⼀个目标是 ExposeInvocationInterceptor,执⾏ invoke(),留意⼊参是 CglibMethodInvocation。
⾥⾯啥都没⼲,持续执⾏ CglibMethodInvocation 的 process()。
3.4.2 第⼆次递归
数组的第⼆个目标是 MethodBeforeAdviceInterceptor,执⾏ invoke()。
3.4.3 第三次递归
数组的第⼆个目标是 AfterReturningAdviceInterceptor,执⾏ invoke()。
执⾏完上⾯逻辑,就会退出递归,咱们看看 invokeJoinpoint() 的执⾏逻辑,其实便是执⾏主⽅法。
再回到第三次递归的⼊⼝,持续执⾏后⾯的切⾯。
切⾯执⾏逻辑,前⾯已经演示过,直接看执⾏⽅法。
后⾯就依次退出递归,整个流程完毕。
3.4.4 规划思路
这块代码,研究了很长时间,因为这个不是单纯的职责链形式。
单纯的职责链形式,目标内部有⼀个⾃身的 next 目标,执⾏完当时目标的⽅法末尾,就会发动 next 目标的执⾏, 直到最终⼀个 next 目标执⾏完毕,或者半途因为某些条件中止执⾏,职责链才会退出。
这⾥ CglibMethodInvocation 目标内部没有 next 目标,全程是经过 interceptorsAndDynamicMethodMatchers ⻓度为 3 的数组操控,依次去执⾏数组中的目标,直到最终⼀个目标执⾏完毕,职责链才会退出。
这个也归于职责链,仅仅完成⽅式不⼀样,后⾯会详细分析,下⾯再讨论⼀下,这些类之间的关系。
咱们的主目标是 CglibMethodInvocation,承继于 ReflectiveMethodInvocation,然后 process() 的核⼼逻辑,其 实都在 ReflectiveMethodInvocation 中。
ReflectiveMethodInvocation 中的 process() 操控整个职责链的执⾏。
ReflectiveMethodInvocation 中的 process() ⽅法,⾥⾯有个⻓度为 3 的数组 interceptorsAndDynamicMethodMatchers,⾥⾯存储了 3 个目标,分别为
- ExposeInvocationInterceptor、
- MethodBeforeAdviceInterceptor、
- AfterReturningAdviceInterceptor。
留意!!!这 3 个目标,都是承继 MethodInterceptor 接⼝。
然后每次执⾏ invoke() 时,⾥⾯都会去执⾏ CglibMethodInvocation 的 process()。
是不是晦涩难明?甭着急,这儿咱们重新再梳理⼀下。
目标和⽅法的关系:
- 接⼝承继:数组中的 3 个目标,都是承继 MethodInterceptor 接⼝,完成⾥⾯的 invoke() ⽅法;
- 类承继:咱们的主目标 CglibMethodInvocation,承继于 ReflectiveMethodInvocation,复⽤它的 process() ⽅法;
- 两者结合(战略形式):invoke() 的⼊参,便是 CglibMethodInvocation,执⾏ invoke() 时,内部会执⾏ CglibMethodInvocation.process(),这个其实便是个战略形式。
可能有同学会说,invoke() 的⼊参是 MethodInvocation,没错!可是 CglibMethodInvocation 也承继了 MethodInvocation,不信⾃⼰能够去看。
执⾏逻辑:
- 程序⼊⼝:是 CglibMethodInvocation 的 process() ⽅法;
- 链式执⾏(衍⽣的职责链形式):process() 中有个包括 3 个目标的数组,依次去执⾏每个目标的 invoke() ⽅ 法。
- 递归(逻辑回退):invoke() ⽅法会执⾏切⾯逻辑,同时也会执⾏ CglibMethodInvocation 的 process() ⽅ 法,让逻辑再⼀次进⼊ process()。
- 递归退出:当数字中的 3 个目标悉数执⾏完毕,流程完毕。
所以这⾥规划奇妙的地⽅,是因为朴实职责链形式,⾥⾯的 next 目标,需求确保⾥⾯的目标类型完全相同。
可是数组⾥⾯的 3 个目标,⾥⾯没有 next 成员目标,所以不能直接⽤职责链形式,那怎么办呢?就独自搞了⼀个 CglibMethodInvocation.process(),经过去⽆限递归 process(),来完成这个职责链的逻辑。
这便是咱们为什么要看源码,学习⾥⾯优秀的规划思路!
四. 总要有总结
咱们再⼩节⼀下,⽂章先介绍了什么是 AOP,以及 AOP 的原理和示例。
之后再分析了 AOP 的源码,分为 3 块:
- 将一切的切⾯都保存在缓存中;
- 取出缓存中的切⾯列表,和 louzai 目标的一切⽅法匹配,拿到归于 louzai 的切⾯列表;
- 创立 AOP 署理目标;
- 经过“职责链 + 递归”,去执⾏切⾯逻辑。
最难的地⽅仍是抠 “切⾯执⾏”的规划思路,尽管流程能⾛通,可是把整个规划思维能总结出来,并讲浅显易懂仍是有难度的。
因为博主现在没有对应大众号,有些内容学习一个老哥 ,他大众号 大众号:楼仔,能够重视下,咱们下期再会!
本文正在参加「金石方案」