Spring AOP概述

1. AOP概述

1.1 官方文档描述

在 SpringFramework 的官方文档中,有专门的一章来介绍 Spring 的 AOP :

docs.spring.io/spring-fram…

在开始介绍正题之前,有两段 AOP 的概述:

Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and objects. (Such concerns are often termed “crosscutting” concerns in AOP literature.)

One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP (meaning you do not need to use AOP if you don’t want to), AOP complements Spring IoC to provide a very capable middleware solution.

面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。 OOP 中模块化的关键单元是类,而在 AOP 中模块化是切面。切面使关注点(如事务管理)的模块化可以跨越多种类型和对象(这种关注在 AOP 的文献中通常被称为 “跨领域” 关注)。

Spring 的关键组件之一是 AOP 框架。尽管 Spring IoC 容器不依赖于 AOP(这意味着您不需要的话就不需要使用 AOP ),但 AOP 是对 Spring IoC 的补充,以提供功能强大的中间件解决方案。

咱来抽取一下这里面的核心内容:

  • AOP 是 OOP 的补充

    • 第 39 章我们通过一些基于 OOP 的设计模式发现,设计模式并不能完全有效的解决这些分散的相同逻辑造成的重复代码

    • AOP 可以将这些重复的代码逻辑抽取为一个切面,通过在运行时动态代理组合进原有的对象,照样能实现预期的效果

  • AOP 关注的是核心切面

    • 第 39 章中我们也看到了,切面可以简单地理解为分散在不同类中的一组相同的逻辑

    • AOP 在对指定类的指定方法的逻辑增强时,就需要直接编写这些增强的逻辑,并切入到原有的代码中

  • AOP 也是 Spring IOC 的补充

    • 如果没有 AOP ,IOC 本身也可以是 Spring 非常强大的特性

    • 只不过 AOP 可以在 IOC 容器中,针对需要的 Bean 去增强原有的功能(如给普通的 Service 实现事务控制)

核心内容不变,还是之前在第 39 章中提到的那些核心内容。

1.2 AOP的核心工作

前面的场景演绎也好,文档的描述也好,总结下来,AOP 要完成的核心工作还是一个:解耦

AOP 将分散在各个类中方法的重复逻辑抽取为一个切面,并在运行时生成代理对象,将这些重复逻辑组合进原有的对象,这其实就是完成了原有业务与扩展逻辑之间的解耦。

通过这种解耦,最大的好处也就显而易见了:业务逻辑只需要关注业务逻辑,每个扩展逻辑也都只关心自己的逻辑,以及切入业务逻辑的位置即可

1.3 面试中如何概述AOP

以下答案仅供参考,可根据个人理解和知识储备进行实际调整:

AOP 面向切面编程,全称 Aspect Oriented Programming ,它是 OOP 的补充。OOP 关注的核心是对象,AOP 的核心是切面(Aspect)。AOP 可以在不修改功能代码本身的前提下,使用运行时动态代理的技术对已有代码逻辑增强。AOP 可以实现组件化、可插拔式的功能扩展,通过简单配置即可将功能增强到指定的切入点。

2. AOP的演变历史

跟前面学习 SpringFramework 框架一样,咱可以先了解一下 AOP 的前生今世。小伙伴们无需掌握,了解一下看个乐呵就完事了。

前面的 AOP 概念引入的那个演变过程咱就不说了,咱首先来看,SpringFramework 还没有出现之前,开源界都有什么 AOP 的解决方案。

2.1 AOP理论的提出

早在 1990 年,有一个名为 Xerox Palo Alto Research Lab (PARC)的组织就对 OOP 的设计思想进行了分析,当时他们就已经分析出 OOP 在重复逻辑抽取时的局限性了。于是他们费尽心思研究出了一套理论,使用它就可以将这些通用的重复逻辑都抽取出来,并在合适的时机把这些逻辑再组合进原始的业务类中。这套理论,就是 AOP 的早期设计。随着研究的不断深入,AOP 的理论和思想也慢慢完善,逐渐形成了一套完整的设计思想。

2.2 第一代AOP的诞生

与上面的时间线类似,当 Xerox Palo Alto Research Lab 这个组织在研究 AOP 的理论的同时,美国 东北大学的一个博士生,和他的团队也在研究着如何解决 OOP 的这个重复逻辑的抽取问题,后来就折腾出来了一套 AOP 的框架,它就是AspectJ。随后,到了 2002 年,AspectJ 转移到 eclipse 开源基金会组织,并逐渐成为 Java 开源社区中最流行的 AOP 框架。

AspectJ 被称为第一代 AOP 的代表,它采用静态字节码编译的方式,使用特殊的编译器,将实现写好的通知逻辑织入到目标类中,这样产生的 .class 字节码文件就是带有增强通知的代理类了。

这种静态 AOP 最大的好处就是快,因为 .class 文件本身就已经是被增强过的了,接下来的动作跟普通的字节码没有任何区别;至于缺点,也很明显,每次修改任何通知都要重新编译所有要被增强的业务类,重新打包工程,这个还是蛮麻烦的。

2.3 第二代AOP的诞生

在 AspectJ 成型之后的一段时间,开源界又出现了一个动态的 AOP (即第二代 AOP )框架,叫 AspectWerkz ,它与 AspectJ 在最初的设计就不一样,它一开始就是搞动态 AOP 的,所以后来在 2005 年 AspectJ 跟 AspectWerkz 达成协议,将 AspectWerkz 的内容合并至 AspectJ 中。从那以后,AspectJ 成为了独有的同时支持静态 AOP 和动态 AOP 的强大的 AOP 框架。

除此之外,JBoss 也有推出它的 AOP 框架,不过现在我们已经接触不到了,毕竟现在谁还在用 J2EE 那一套呢。。。

要论述动态 AOP 的优点,那就是不再需要频繁的编译业务类了,切面修改完毕后只需要单独重新编译切面类即可,其余的部分都不太需要变化;当然,缺点也就随之而来了,由于不是在字节码的编译环节完成的通知织入,那就需要在类加载期 / 运行期动态织入增强逻辑,这会在一定程度上对性能有所影响

2.4 SpringFramework与AspectJ

既然 AspectJ 那么强大了,SpringFramework 可坐不住,它得想个办法把 AspectJ 搞下来。所以后来的 SpringFramework 2.0 版本,它直接声明了对 AspectJ 的支持,可以使用 AspectJ 的方式定义切面类、声明通知方法等等。不过话又说回来,如果 SpringFramework 直接把 AspectJ 的东西搞过来,似乎一点都不现实(因为当时的 AspectJ 已经在开源界有一席之地了),那 SpringFramework 的创始人们在权衡这件事的时候就在思考了,如果只是用它的写法和定义方式,最底层还是用我自己的逻辑来封装,那岂不是最好?既兼容了主流的技术,又不会影响我本身的底层架构设计。得,那就这么办,所以 SpringFramework 对 AspectJ 的兼容,只是使用了它的声明和定义方式,具体的底层实现还是 SpringFramework 原生的那一套

随着后续 SpringFramework 的地位逐渐强大,其它的 AOP 框架也就慢慢销声匿迹了,大家也就只记得 SpringFramework + AspectJ 了。

3. AOP的基本术语

3.1 官方文档的介绍

在 SpringFramework 的官方文档中,紧接着 AOP 的描述之后,就是 Spring 定义的 AOP 的相关术语:

docs.spring.io/spring-fram…

那里面的内容有点多,而且有些晦涩难懂,小册在这里就不贴出来了,有需要的小伙伴们可以点上面的链接跳转。

3.2 大白话讲解AOP术语

小册考虑到部分小伙伴是刚接触 AOP ,或者以前没有彻底理解 AOP 的这些基本术语,所以我打算换一种方式,用大白话和现实的场景来帮助小伙伴们学习 AOP 中的那些术语。

3.2.0 场景预设

为方便解释 AOP 中的术语,小册在这里顺便再来搞一个场景:

在这个场景中,左边的主管视为 “原始对象” ,主管可提供账户充值、账号解封等业务,意为一个 Class 中定义的几个方法;中间的业务经理视为 “中间的代理层” ,他平时招揽客人,并且将客人的需求传达给里面的主管;右边开门办业务的视为 “客户端” ,办业务的时候都是由它发起。

下面咱开始解释。

3.2.1 Target:目标对象

这个应该是最好理解的了,目标对象就是被代理的对象。上面的场景中很明显左边的主管就是 Target

反映到前面的动态代理的例子中,这个 partner 就可以称作 Target

public static Partner getPartner(int money) {
    // partner即为目标对象
    Partner partner = partners.remove(0);
    return (Partner) Proxy.newProxyInstance(......);
}

3.2.2 Proxy:代理对象

也是很好理解吧,代理对象就是上面代码中 Proxy.newProxyInstance 返回的结果。

在上面的场景中,中间的业务经理 + 左边的主管,组合形成一个代理对象(代理对象中还包含原始对象本身)。

3.2.3 JoinPoint:连接点

所谓连接点,可以简单的理解为目标对象的所属类中,定义的所有方法。由于 SpringFramework 支持的连接点只有方法,所以我们这样理解就没错。在上面的场景中,很明显主管提供的几项业务(账号充值、账户解封)就属于连接点

反映到前面动态代理的例子中,**Partner** 接口中的两个方法就叫连接点

public interface Partner {
    void receiveMoney(int money);
    void playWith(Player player);
}

3.2.4 Pointcut:切入点

切入点,它的含义是那些被拦截 / 被增强的连接点。这个概念似乎不是很好理解了,咱继续看上面的场景。中间的业务经理在给主管传话的时候,并不是每次都实话实说,但也不都是瞎说,很明显他是看到有充值这样的涉及钱的业务,就开始胡说八道了,而没有涉及到钱的业务,他就如实转述。那我们是不是可以这样去理解:代理层会选择目标对象的一部分连接点作为切入点,在目标对象的方法执行前 / 后作出额外的动作

所以,由这个解释,是不是就比较容易理解了?切入点与连接点的关系应该是包含关系:切入点可以是 0 个或多个(甚至全部)连接点的组合

注意,切入点一定是连接点,连接点不一定是切入点

3.2.5 Advice:通知

Advice 直接翻译过来叫通知,但这个概念似乎很抽象,所以我打算换一个词:增强的逻辑,也就是增强的代码

这下就好理解多了吧!上面的场景中,业务经理发现有人要充值的时候,它并没有直接传话给主管,而是先执行了他自己的逻辑:胡说八道,而在传话之前的这个胡说八道,就是业务主管针对账户充值这个连接点的增强逻辑

由此可以得出一个这样的结论:Proxy 代理对象 = Target 目标对象 + Advice 通知

所以是不是突然意识到一个问题,切入点和通知是要配合在一起使用的,有了切入点之后,需要搭配上增强的逻辑,才能算是给目标对象进行了代理、增强。

3.2.6 Aspect:切面

紧接着我们说切面,Aspect 切面 = PointCut 切入点 + Advice 通知,就这么简单。

前面我们写的 InvocationHandler 的匿名内部类也好,MethodInterceptor 的匿名内部类也好,这些都可以看作是切面

不过实际上切面不仅仅是包含通知,还有一个不常见的部分是引介,马上就说到了。

3.2.7 Weaving:织入

从名字上听起来,它有点像一个动作,而且我们先瞎猜一手,也能猜个八九不离十:织入就是将 Advice 通知应用到 Target 目标对象,进而生成 Proxy 代理对象的过程

Proxy 代理对象 = Target 目标对象 + Advice 通知,这个算式中的加号,就是织入。试想,目标对象和通知都有了,得需要一个动作将它们两个绑定到一起,就好比上面的场景中,主管找到这个在外招呼客人的经理后,也是要签了合同或者协议,经理才开始干活的呀。

所以,这个织入的动作,就比较容易理解了吧。

3.2.8 Introduction:引介

引介 / 引入,这个概念对标的是通知,通知是针对切入点提供增强的逻辑,而引介是针对 Class 类,它可以在不修改原有类的代码的前提下,在运行期为原始类动态添加新的属性 / 方法

这个引介吧,在目前的企业应用、场景中很少出现了,所以咱对它的重视程度一定要放低,当然感兴趣的话学习一下也是可以的,咱后面 AOP 高级部分会讲解到的。

3.3 通知的类型

这一章的最后,还有一个事情没有说,那就是通知的类型。

在 SpringFramework 的官方文档 AOP 术语的介绍之后,紧跟着就说了 Spring 中定义的通知的类型。SpringFramework 中支持的通知的类型包含 5 种,这些通知的类型是基于 AspectJ 的,咱到第 43 章会再次提到,这里咱先简单看一下:

  • Before 前置通知:目标对象的方法调用之前触发

  • After 后置通知:目标对象的方法调用之后触发

  • AfterReturning 返回通知:目标对象的方法调用完成,在返回结果值之后触发

  • AfterThrowing 异常通知:目标对象的方法运行中抛出 / 触发异常后触发

    • 注意一点,AfterReturning 与 AfterThrowing 两者是互斥的!如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。

  • Around 环绕通知:编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法。

这几种通知的体现,目前我们只编写过环绕通知,而这些环绕通知,其实就是 InvocationHandlerMethodInterceptor 的匿名内部类:

在之前写的代码中,method.invoke 方法很明显是对目标方法的调用,而这之前和之后的代码,加到一起,就是环绕通知的内容。

最后更新于