SpringAop源码

深度剖析Spring AOP源码,图文详解,小白也能看明白。

一:概述以及目录

前两篇的源码解析,涉及到很多基础知识,但是源码的解读都不难,这篇⽂章刚好相反,依赖的基础知识不多,但 是源码⽐较难懂。 下⾯我会简单介绍⼀下 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(),⾛下⾯逻辑。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

  1. 先获取 louzai 类的所有切⾯列表;
  2. 创建⼀个 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 代理对象;
  • 通过“责任链 + 递归”,去执⾏切⾯逻辑。

最难的地⽅还是抠 “切⾯执⾏”的设计思路,虽然流程能⾛通,但是把整个设计思想能总结出来,并讲通俗易懂还是有难度的。
————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/ww2651071028/article/details/129486012

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值