76 张图,剖析 Spring AOP 源码

发表于 1年以前  | 总阅读数:318 次

前两篇分享的 Spring 源码,反响非常不错,这个是源码系列的第 3 篇

前两篇的源码解析,涉及到很多基础知识,但是源码的解读都不难,这篇文章刚好相反,依赖的基础知识不多,但是源码比较难懂。

下面我会简单介绍一下 AOP 的基础知识,以及使用方法,然后直接对源码进行拆解。

不 BB,上文章目录。

1 . 基础知识

1.1 什么是 AOP ?

AOP 的全称是 “Aspect Oriented Programming”,即面向切面编程

在 AOP 的思想里面,周边功能(比如性能统计,日志,事务管理等)被定义为切面,核心功能和切面功能分别独立进行开发,然后把核心功能和切面功能“编织”在一起,这就叫 AOP。

AOP 能够将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

1.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):把整个目标方法包裹起来,在被调用前和调用之后分别调用通知功能。

1.3 AOP 简单示例

新建 Louzai 类:

@Data
@Service
public class Louzai {

    public void everyDay() {
        System.out.println("睡觉");
    }
}

添加 LouzaiAspect 切面:

@Aspect
@Component
public class LouzaiAspect {

    @Pointcut("execution(* com.java.Louzai.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");
        Louzai louzai = (Louzai) context.getBean("louzai");
        louzai.everyDay();
    }
}

输出:

吃饭
睡觉
打豆豆。。。

这个示例非常简单,“睡觉” 加了前置和后置通知,但是 Spring 在内部是如何工作的呢?

1.4 Spring AOP 工作流程

为了方便大家能更好看懂后面的源码,我先整体介绍一下源码的执行流程,让大家有一个整体的认识,否则容易被绕进去。

整个 Spring AOP 源码,其实分为 3 块,我们会结合上面的示例,给大家进行讲解。

第一块就是前置处理,我们在创建 Louzai Bean 的前置处理中,会遍历程序所有的切面信息,然后将切面信息保存在缓存中,比如示例中 LouzaiAspect 的所有切面信息。

第二块就是后置处理,我们在创建 Louzai Bean 的后置处理器中,里面会做两件事情:

  • 获取 Louzai 的切面方法:首先会从缓存中拿到所有的切面信息,和 Louzai 的所有方法进行匹配,然后找到 Louzai 所有需要进行 AOP 的方法。
  • 创建 AOP 代理对象:结合 Louzai 需要进行 AOP 的方法,选择 Cglib 或 JDK,创建 AOP 代理对象。

第三块就是执行切面,通过“责任链 + 递归”,去执行切面。

2 . 源码解读

注意:Spring 的版本是 5.2.15.RELEASE,否则和我的代码不一样!!!

除了原理部分,上面的知识都不难,下面才是我们的重头戏,让你跟着楼仔,走一遍代码流程。

2.1 代码入口

这里需要多跑几次,把前面的 beanName 跳过去,只看 louzai。

进入 doGetBean(),进入创建 Bean 的逻辑。

2.2 前置处理

主要就是遍历切面,放入缓存。

这里是重点!敲黑板!!!

  1. 我们会先遍历所有的类;
  2. 判断是否切面,只有切面才会进入后面逻辑;
  3. 获取每个 Aspect 的切面列表;
  4. 保存 Aspect 的切面列表到缓存 advisorsCache 中。

到这里,获取切面信息的流程就结束了,因为后续对切面数据的获取,都是从缓存 advisorsCache 中拿到。

下面就对上面的流程,再深入解读一下。

2.2.1 判断是否是切面

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

2.2.2 获取切面列表

进入到 getAdvice(),生成切面信息。

2.3 后置处理

主要就是从缓存拿切面,和 louzai 的方法匹配,并创建 AOP 代理对象。

进入 doCreateBean(),走下面逻辑。

这里是重点!敲黑板!!!

  1. 先获取 louzai 类的所有切面列表;
  2. 创建一个 AOP 的代理对象。

2.3.1 获取切面

我们先进入第一步,看是如何获取 louzai 的切面列表。

进入 buildAspectJAdvisors(),这个方法应该有印象,就是前面将切面信息放入缓存 advisorsCache 中,现在这里就是要获取缓存。

再回到 findEligibleAdvisors(),从缓存拿到所有的切面信息后,继续往后执行。

2.3.2 创建代理对象

有了 louzai 的切面列表,后面就可以开始去创建 AOP 代理对象。

这里是重点!敲黑板!!!

这里有 2 种创建 AOP 代理对象的方式,我们是选用 Cglib 来创建。

我们再回到创建代理对象的入口,看看创建的代理对象。

2.4 切面执行

通过 “责任链 + 递归”,执行切面和方法。

前方高能!这块逻辑非常复杂!!!

下面就是“执行切面”最核心的逻辑,简单说一下设计思路:

  1. 设计思路:采用递归 + 责任链的模式;
  2. 递归:反复执行 CglibMethodInvocation 的 proceed();
  3. 退出递归条件:interceptorsAndDynamicMethodMatchers 数组中的对象,全部执行完毕;
  4. 责任链:示例中的责任链,是个长度为 3 的数组,每次取其中一个数组对象,然后去执行对象的 invoke()。

因为我们数组里面只有 3 个对象,所以只会递归 3 次,下面就看这 3 次是如何递归,责任链是如何执行的,设计得很巧妙!

2.4.1 第一次递归

数组的第一个对象是 ExposeInvocationInterceptor,执行 invoke(),注意入参是 CglibMethodInvocation。

里面啥都没干,继续执行 CglibMethodInvocation 的 process()。

2.4.2 第二次递归

数组的第二个对象是 MethodBeforeAdviceInterceptor,执行 invoke()。

2.4.3 第三次递归

数组的第二个对象是 AfterReturningAdviceInterceptor,执行 invoke()。

执行完上面逻辑,就会退出递归,我们看看 invokeJoinpoint() 的执行逻辑,其实就是执行主方法。

再回到第三次递归的入口,继续执行后面的切面。

切面执行逻辑,前面已经演示过,直接看执行方法。

后面就依次退出递归,整个流程结束。

2.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(),来实现这个责任链的逻辑。

这就是我们为什么要看源码,学习里面优秀的设计思路!

3 . 总结

我们再小节一下,文章先介绍了什么是 AOP,以及 AOP 的原理和示例。

之后再剖析了 AOP 的源码,分为 3 块:

  • 将所有的切面都保存在缓存中;
  • 取出缓存中的切面列表,和 louzai 对象的所有方法匹配,拿到属于 louzai 的切面列表;
  • 创建 AOP 代理对象;
  • 通过“责任链 + 递归”,去执行切面逻辑。

这篇文章,是 Spring 源码解析的第 3 篇,也是感觉最难的一篇,光图解代码就扣了 6 个小时,整个人都被扣麻了。

最难的地方还不是抠图,而是 “切面执行”的设计思路,虽然流程能走通,但是把整个设计思想能总结出来,并讲得能让大家明白,还是非常不容易的。

今天的源码解析就到这,Spring 相关的源码,还有哪些是大家想学习的呢,可以给楼仔留言。

本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/NDDf3GBy5hJ4diuSgKZGog

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:8月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:8月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:8月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:8月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:8月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:8月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:8月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:8月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:8月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:8月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:8月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:8月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:8月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:8月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:8月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:8月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:8月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:8月以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  236889次阅读
vscode超好用的代码书签插件Bookmarks 1年以前  |  6999次阅读
 目录