【重写SpringFramework】第二章aop模块:本章小结(chapter 2-8)

1. 前言

在 Spring 框架中,IOC 和 AOP 共同构成了整个体系的基础。第一章介绍了 IOC 机制,本章关注的是 AOP 功能。相对于 beans 模块而言,aop 模块的功能更加单一,因此从代码层面来说,aop 模块有一条非常清晰的主线,分为三个阶段。

  1. 准备工作:对 AOP 规范进行分析,得到的结论是,要实现 AOP 功能需要满足三要素,即增强、切面和代理。

  2. 主体部分:实现了 AOP 的三要素。首先是增强,对 Advice 接口进行扩展,主要关注方法的拦截和调用。然后是切面,通过 Advisor 组件完成构建切面的工作。最后是代理,将增强和切面织入到目标对象之中。显而易见,增强、切面、代理通过拼积木的方式逐步扩展,直至形成一个整体。

  3. 扩展部分:自动代理除了将创建代理的流程自动化之外,更重要的是将 AOP 功能与 IOC 容器联系到了一起。这种巧妙的设计,正是画龙点睛之笔。

2. AOP 规范

AOP 联盟(aop alliance)完成了 AOP 规范的顶层设计,通过寥寥数个接口对 AOP 做了高度的抽象。总的来说,AOP 规范可以分为两个部分。其中 Advice 表示增强的语义,Joinpoint 表示系统的执行点。增强逻辑一定是作用于某段代码上的,Joinpoint 充当了桥梁的作用,使得 Advice 可以嵌入到业务代码中。

静态连接点表示程序中的某处位置,AccessbileObject 表示一个可访问的对象,包括方法、构造器和字段三种类型。Joinpoint 与 Advice 是相关联的,因此每个可访问对象都有一组与之对应的 Joinpoint 和 Advice 实例。

  • MethodInvocation 表示方法调用,对应的增强组件是 MethodInterceptor
  • ConstructorInvocation 表示构造器调用,对应的增强组件是 ConstructorInterceptor
  • FieldAccess 表示字段访问,对应的增强组件是 FieldInterceptor。(虽然没有定义相关接口,但这些内容是可以推导出来的)

3. Spring Advice

Spring 对增强组件的处理,主要体现在两个方面。首先,理论上来说可访问对象有三种,AOP 规范定义了两种,而 Spring 只考虑方法的拦截和调用。这是因为在应用程序中,业务逻辑都出现在方法中,与字段和构造器无关。

其次,对于方法来说,拦截的时机是必须要考虑的。鉴于此,Spring AOP 对 Advice 进一步抽象。具体来说,Advice 的子接口分别负责前置通知、返回通知和异常通知,而 MethodInterceptor 接口的子类则负责对应通知的调用时机。以前置通知为例,MethodBeforeAdvice 的关注点是增强逻辑,而 MethodBeforeAdviceInterceptor 则注重增强逻辑切入的时机。这样一来,拦截逻辑和调用时机被分离开来,降低了系统的耦合度,用户只需要关心拦截逻辑的实现即可。

当前置通知、返回通知和异常通知同时作用于一个方法时,便构成了一个环绕通知,这种环绕通知仅具有逻辑上的意义。此外,我们还可以通过 MethodInterceptor 接口来实现环绕通知,二者在功能上等价的。

4. Advisor

4.1 概述

Advice 提供了增强的语义,可以对所有的方法进行增强。对于 AOP 来说,同样重要的还有切面,需要判断增强逻辑应用在哪些方法上。Spring 在处理切面的问题上进行了综合考量,通过 Advisor 组件解决了三个问题。

  • 增强:持有一个 Advice 实例,对于切面来说最终目的是应用增强逻辑。
  • 切面:构建切面的具体方式有两种,即切点和引入。
  • 适配:系统中存在多种类型的增强组件,需要进行统一的抽象。

4.2 切面

Spring 提供了两种构建切面的方式,一是切点,二是引入。为了简化代码,我们只关心切点的实现。切点实际上是一个组合体,由 ClassFilter 和 MethodMatcher 构成,前者对类进行过滤,后者对方法进行匹配。大多数情况下,我们只关心方法的匹配。

方法匹配又分为静态匹配和动态匹配,所谓静态匹配是指方法签名,包括方法上的注解之类,这些信息在编译期就能确定。所谓动态匹配是指除了静态匹配的内容,还要匹配运行时传入方法的参数。我们仅实现 Spring AOP,因此格外关心静态匹配。至于动态匹配,是通过 AspectJ 构建切面时应用的。

综上所述,Spring AOP 构建切面的主要方式是对方法进行静态匹配。对于框架自身来说,Spring AOP 的功能是完全够用的。在第四章 tx 模块中,我们将通过 Spring AOP 实现事务功能。至于 AspectJ 的 AOP 实现,感兴趣的读者可尝试完成。

4.3 适配

在 AOP 规范的定义中,存在两种增强对象,即 Advice 和 MethodInterceptor 接口。此外,Spring 还定义了 Advisor 组件。这样一来,同时存在三种增强组件,因此有必要对所有的增强组件进行统一的抽象,这一工作是由 AdvisorAdapter 接口完成的。

从原理上来说,适配后的类型是 MethodInterceptor,因此实际上只需要对 Advice 类型进行适配。我们知道,Spring 将 Advice 分为三种,即前置通知、返回通知、异常通知,相应地定义三个适配器对象即可。如下所示:

  • MethodBeforeAdviceAdapter:如果是前置通知,包装成 MethodBeforeAdviceInterceptor
  • AfterReturningAdviceAdapter:如果是返回通知,包装成 AfterReturningAdviceInterceptor
  • ThrowsAdviceAdapter:如果是异常通知,包装成 ThrowsAdviceInterceptor

5. AOP 代理

5.1 概述

AOP 功能最终的落脚点是一个对象(的方法),因此需要将增强和切面织入到对象之中。Spring 通过代理模式实现了对目标对象的整合,AOP 代理的构成比较复杂,大体分为四个部分:

  • ProxyFactory 表示代理工厂,屏蔽了创建代理的细节实现,负责设置相关属性和创建对象,相当于兼具 BeanDefinition 与 BeanFactory 的功能。
  • AopProxy 表示代理对象,根据创建方式分为两种,JdkDynamicAopProxy 表示 JDK 动态代理,CglibAopProxy 表示 Cglib 代理。
  • TargetSource 表示目标对象,SingletonTargetSource 表示 Spring 容器托管的单例,LazyInitTargetSource 的作用是包装延迟初始化的单例。
  • Advisor 组件整合了增强和切面的功能,与 AopProxy 一起实现了 Spring AOP 的基本功能。

5.2 AOP 代理的本质

AOP 代理实际上是个包装类,持有一个目标对象,以及一个 Advisor 集合。一个类中有 N 个方法,所有方法一共用到了 M 种增强。换句话说,一个方法可以有多个增强,也可以没有任何增强,这种情况被当作普通方法调用。我们发现,从创建代理对象到调用方法的过程中,一共出现了两次过滤。第一次是为目标对象寻找合适的 Advisor 集合,这是面向类的粗粒度过滤。第二次是调用方法时,寻找适合当前方法的 Advisor 并转换成 MethodInterceptor,也就是拦截器链,这是面向方法的细粒度过滤。

如下图所示,代理对象持有一个目标对象,目标对象拥有 method1 和 method2 两个方法。此外,代理对象还持有一组 Advisor 集合,这是目标对象的所有方法一共用到的。但对于具体方法来说,假设 method1 是一个增强方法,那么 AOP 代理会构建一个拦截器链,先于目标方法之前执行增强逻辑。假设 method2 不是增强方法,也就是说 AOP 代理不能构建拦截器链,当作普通方法调用即可。

8.5 aop代理的原理.png

综上所述,AOP 代理的实质是通过代理对象接管对目标对象的访问,在调用目标方法前寻找拦截器链,依次执行每个拦截器的增强逻辑,最后调用目标方法。关键在于必须通过代理对象来访问,以此触发拦截器链的执行。

5.3 AOP 失效的问题

当我们在一个增强方法中调用同一个类中的另一个增强方法,第二个方法的增强逻辑会失效,这就是 AOP 失效的问题。这是因为调用第一个方法时,引用指向的是代理对象。首先执行的是拦截器链,也就是增强逻辑。当进入第一个方法内部,this 引用指向的是目标对象,此时不存在拦截器链,第二个方法只是一个普通方法。

在 AOP 代理回调的过程中,代理对象被绑定到当前线程上,我们可以通过 AopContext 工具类的 currentProxy 方法获取代理对象,前提是将 AopConfig 类的 exposeProxy 属性设置为 true。通过对外暴露代理,解决了 AOP 失效的问题。具体来说,在增强方法中获取代理对象,然后通过代理对象来调用其他增强方法。

但这种方式也有不足之处。代理对象保存在 AopContext 的 ThreadLocal 类型字段中,也就是说代理对象是和当前线程绑定的。如果尝试在新线程中获取代理对象,会抛出异常。这一缺陷与 AOP 代理的底层逻辑有关,应当尽量避免这一情况的出现。

6. 自动代理

6.1 概述

在实际使用中,我们不可能一个个地去创建代理对象,迫切需要一种能自动完成创建代理对象的机制。具体思路是,依托 Spring 容器强大的管理能力,在 Bean 创建流程中检查对象是否需要被代理,从而实现自动创建代理的功能。自动代理的功能是通过 BeanPostProcessor 来扩展的,ProxyProcessorSupport 作为父类提供了两种基本实现,简单介绍如下:

  • AbstractAdvisingBeanPostProcessor:持有一个 Advisor 实例,需要手动设置。比如 AsyncAnnotationBeanPostProcessor 实现了异步操作,允许声明了 @Async 注解的方法在新线程中执行。这种代理的功能单一,直接将 AsyncAnnotationAdvisor 作为默认设置,外界不能进行干预。
  • AbstractAutoProxyCreator:持有一组 Advisor 实例,并且支持自动设置。具体做法是检索 Spring 容器中所有的 Advisor 实例,然后过滤出符合条件的实例,可能存在多个。在实际使用中,AbstractAutoProxyCreator 具有通用性,因此我们主要关注该类的实现。

6.2 AbstractAutoProxyCreator

AbstractAutoProxyCreator 实现了 InstantiationAwareBeanPostProcessor 接口及其父接口 BeanPostProcessor 的三个方法,它们都能用来创建代理对象,区别在于调用时机和用途。这三个方法按照执行顺序,分别介绍如下:

  • postProcessBeforeInstantiation 方法:在对象实例化之前执行,这种方式比较特殊,比如延迟初始化的实现。
  • getEarlyBeanReference 方法:在填充对象的过程中执行,具体来说是在依赖解析时调用,专门负责处理代理对象的循环依赖。
  • postProcessAfterInitialization 方法:在对象初始化之后执行,是创建代理对象的主要方式。

8.6 创建代理对象的时机.png

为了确保创建代理的操作只执行一次,这三种创建代理的方式是互斥的。具体来说,如果在实例化之前创建对象,那么就不会执行后两种方式。在这一基础上,后两种创建代理的方式也是互斥的。

8.7 自动代理的实现方式.png

7. 关于 AspectJ

Spring AOP 是一个简单的面向切面编程的解决方案,事实上,AspectJ 这种成熟的框架也是一个很好的选择。我们没有介绍基于 AspectJ 的 AOP 实现,主要原因有两点。

  1. AspectJ 的难度较大,不利于学习。事实上,我们已经搭建起了 AOP 的基本骨架,AspectJ 的实现主要是填充一些细节,比如一笔带过的引入和动态方法匹配等。另外,还有就是 AspectJ 自身定义的组件。这些内容自成体系,且异常琐碎,如果展开来讲要占用大量篇幅,偏离了本教程的主旨。
  2. 虽然 AspectJ 的功能很强大,但对于本教程来说,Spring AOP 的功能完全够用。在第四章 tx 模块中,事务管理就是通过 Spring AOP 实现的。

综上所述,我们通过 Spring AOP 介绍了面向切面编程的思想,逐一厘清了��多概念,并实现了一个基本可用的 AOP 框架。至于 AspectJ 的实现,感兴趣的读者可以尝试自行完成,从而加深对本章所涉及内容的理解。

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值