觉得不错请按下图操作,掘友们,哈哈哈!!!

Spring源码解析-Spring AOP

一:概述以及目录

前两篇分享的 Spring 源码,【Spring源码解析-Bean ⽣命周期】,【Spring源码解析-Spring 循环依赖】,这个是源码系列的第 3 篇。

前两篇的源码解析,涉及到很多根底知识,可是源码的解读都不难,这篇⽂章刚好相反,依赖的根底知识不多,但 是源码⽐较难明。 下⾯我会简略介绍⼀下 AOP 的根底知识,以及使⽤⽅法,然后直接对源码进⾏拆解。目录如下:

Spring源码解析-Spring 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 块,咱们会结合上⾯的示例,给⼤家进⾏解说。

Spring源码解析-Spring AOP

第⼀块便是前置处理,咱们在创立 Model Bean 的前置处理中,会遍历程序一切的切⾯信息,然后将切⾯信息保存 在缓存中,⽐如示例中 ModelAspect 的一切切⾯信息。

第⼆块便是后置处理,咱们在创立 Model Bean 的后置处理器中,⾥⾯会做两件工作:

  • 获取 Model 的切⾯⽅法:⾸先会从缓存中拿到一切的切⾯信息,和 Model 的一切⽅法进⾏匹配,然后找到 Louzai 一切需求进⾏ AOP 的⽅法。
  • 创立 AOP 署理目标:结合 Model 需求进⾏ AOP 的⽅法,选择 Cglib 或 JDK,创立 AOP 署理目标。

Spring源码解析-Spring AOP

第三块便是执⾏切⾯,经过“职责链 + 递归”,去执⾏切⾯。

三:源码解读

留意:Spring 的版本是 5.2.15.RELEASE,不然和我的代码不⼀样!!!

3.1 代码⼊⼝

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

这⾥需求多跑⼏次,把前⾯的 beanName 跳过去,只看 Model。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

进⼊ doGetBean(),进⼊创立 Bean 的逻辑。

Spring源码解析-Spring AOP

3.2 前置处理

Spring源码解析-Spring AOP

首要便是遍历切⾯,放⼊缓存。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

这⾥是要点!敲⿊板!!!

    1. 咱们会先遍历一切的类;
    1. 判断是否切⾯,只要切⾯才会进⼊后⾯逻辑;
    1. 获取每个 Aspect 的切⾯列表;
    1. 保存 Aspect 的切⾯列表到缓存 advisorsCache 中。

Spring源码解析-Spring AOP

到这⾥,获取切⾯信息的流程就完毕了,因为后续对切⾯数据的获取,都是从缓存 advisorsCache 中拿到

下⾯就对上⾯的流程,再深⼊解读⼀下。

3.2.1 判断是否是切⾯

上图的第 2 步,逻辑如下:

Spring源码解析-Spring AOP

3.2.2 获取切⾯列表

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

进⼊到 getAdvice(),⽣成切⾯信息。

Spring源码解析-Spring AOP

3.3 后置处理

Spring源码解析-Spring AOP

首要便是从缓存拿切⾯,和 model 的⽅法匹配,并创立 AOP 署理目标。

Spring源码解析-Spring AOP

进⼊ doCreateBean(),⾛下⾯逻辑。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

这⾥是要点!敲⿊板!!!

  1. 先获取 louzai 类的一切切⾯列表;
  2. 创立⼀个 AOP 的署理目标。

Spring源码解析-Spring AOP

3.3.1 获取切⾯

咱们先进⼊第⼀步,看是怎么获取 model 的切⾯列表。

Spring源码解析-Spring AOP

进⼊ buildAspectJAdvisors(),这个⽅法应该有形象,便是前⾯将切⾯信息放⼊缓存 advisorsCache 中,现在这⾥ 便是要获取缓存。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

再回到 findEligibleAdvisors(),从缓存拿到一切的切⾯信息后,持续往后执⾏。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

3.3.2 创立署理目标

有了 model 的切⾯列表,后⾯就能够开端去创立 AOP 署理目标。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

这⾥是要点!敲⿊板!!!

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

咱们再回到创立署理目标的⼊⼝,看看创立的署理目标。

Spring源码解析-Spring AOP

3.4 切⾯执⾏

Spring源码解析-Spring AOP
经过 “职责链 + 递归”,执⾏切⾯和⽅法。 这⾥有 2 种创立 AOP 署理目标的⽅式,咱们是选⽤ Cglib 来创立。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

前⽅⾼能!这块逻辑⾮常杂乱!!!

Spring源码解析-Spring AOP

下⾯便是“执⾏切⾯”最核⼼的逻辑,简略说⼀下规划思路:

    1. 规划思路:采⽤递归 + 职责链的形式;
    1. 递归:反复执⾏ CglibMethodInvocation 的 proceed();
    1. 退出递归条件:interceptorsAndDynamicMethodMatchers 数组中的目标,悉数执⾏完毕;
    1. 职责链:示例中的职责链,是个⻓度为 3 的数组,每次取其中⼀个数组目标,然后去执⾏目标的 invoke()。

Spring源码解析-Spring AOP

因为咱们数组⾥⾯只要 3 个目标,所以只会递归 3 次,下⾯就看这 3 次是怎么递归,职责链是怎么执⾏的,规划得 很奇妙!

3.4.1 第⼀次递归

数组的第⼀个目标是 ExposeInvocationInterceptor,执⾏ invoke(),留意⼊参是 CglibMethodInvocation。

Spring源码解析-Spring AOP

⾥⾯啥都没⼲,持续执⾏ CglibMethodInvocation 的 process()。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

3.4.2 第⼆次递归

数组的第⼆个目标是 MethodBeforeAdviceInterceptor,执⾏ invoke()。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

3.4.3 第三次递归

数组的第⼆个目标是 AfterReturningAdviceInterceptor,执⾏ invoke()。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP
Spring源码解析-Spring AOP

执⾏完上⾯逻辑,就会退出递归,咱们看看 invokeJoinpoint() 的执⾏逻辑,其实便是执⾏主⽅法。

Spring源码解析-Spring AOP

Spring源码解析-Spring AOP

再回到第三次递归的⼊⼝,持续执⾏后⾯的切⾯。

Spring源码解析-Spring AOP

切⾯执⾏逻辑,前⾯已经演示过,直接看执⾏⽅法。

Spring源码解析-Spring AOP

后⾯就依次退出递归,整个流程完毕。

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 接⼝。

Spring源码解析-Spring AOP

然后每次执⾏ 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 署理目标;
  • 经过“职责链 + 递归”,去执⾏切⾯逻辑。

最难的地⽅仍是抠 “切⾯执⾏”的规划思路,尽管流程能⾛通,可是把整个规划思维能总结出来,并讲浅显易懂仍是有难度的。

因为博主现在没有对应大众号,有些内容学习一个老哥 ,他大众号 大众号:楼仔,能够重视下,咱们下期再会!

本文正在参加「金石方案」