一、简介
如今有多个可用的 AOP 库,这些库需要能够回答许多问题:
- 它与我现有的或新的应用程序兼容吗?
- 哪里可以实现AOP?
- 它与我的应用程序集成的速度有多快?
- 性能开销是多少?
在本文中,我们将回答这些问题并介绍 Spring AOP 和 AspectJ——两个最流行的 Java AOP 框架。
2.AOP概
在开始之前,让我们对术语和核心概念进行快速、高层次的回顾:
- 方面 – 分散在应用程序中多个位置的标准代码/功能,通常与实际业务逻辑(例如事务管理)不同。每个方面都侧重于特定的横切功能
- 连接点——它是程序执行过程中的一个特定点,例如方法执行、构造函数调用或字段赋值
- 建议 – 方面在特定连接点采取的操作
- 切入点——匹配连接点的正则表达式。每次任何连接点与切入点匹配时,都会执行与该切入点关联的指定建议
- 编织——将方面与目标对象链接起来以创建建议对象的过程
3. Spring AOP和AspectJ现在,让我们从多个方面来讨论 Spring AOP 和 AspectJ,例如功能、目标、编织、内部结构、连接点和简单性。
3.1. 能力和目标
简单地说,Spring AOP 和 AspectJ 有不同的目标。
Spring AOP 旨在提供跨 Spring IoC 的简单 AOP 实现,以解决程序员面临的最常见问题。它并不是一个完整的 AOP 解决方案——它只能应用于由 Spring 容器管理的 bean。
另一方面,AspectJ是原始的AOP技术,旨在提供完整的AOP解决方案。它比 Spring AOP 更健壮,但也复杂得多。还值得注意的是,AspectJ 可以应用于所有域对象。
3.2. 编织
AspectJ 和 Spring AOP 都使用不同类型的编织,这会影响它们在性能和易用性方面的行为。
AspectJ 使用三种不同类型的编织:
- 编译时编织:AspectJ 编译器将方面和应用程序的源代码作为输入,并生成编织类文件作为输出
- 编译后编织:这也称为二进制编织。它用于将现有的类文件和 JAR 文件与我们的方面编织在一起
- 加载时编织:这与之前的二进制编织完全相同,不同之处在于编织被推迟,直到类加载器将类文件加载到 JVM
有关 AspectJ 本身的更多深入信息,请阅读本文。
正如 AspectJ 使用编译时和类加载时编织一样,Spring AOP 使用运行时编织。
通过运行时编织,各个方面在应用程序执行期间使用目标对象的代理进行编织 - 使用 JDK 动态代理或 CGLIB 代理(在下一点中讨论):
3.3. 内部结构及应用
Spring AOP是一个基于代理的AOP框架。这意味着要实现目标对象的方面,它将创建该对象的代理。这是通过以下两种方式之一实现的:
- JDK动态代理——Spring AOP的首选方式。每当目标对象实现一个接口时,就会使用 JDK 动态代理
- CGLIB代理——如果目标对象没有实现接口,则可以使用CGLIB代理
我们可以从官方文档中了解更多关于Spring AOP代理机制的信息。
另一方面,AspectJ 在运行时不执行任何操作,因为类是直接使用方面编译的。
因此与 Spring AOP 不同,它不需要任何设计模式。为了将方面编织到代码中,它引入了称为 AspectJ 编译器 (ajc) 的编译器,通过它我们可以编译程序,然后通过提供一个小型(< 100K)运行时库来运行它。
3.4. 连接点
在3.3节中,我们展示了Spring AOP是基于代理模式的。因此,它需要对目标 Java 类进行子类化并相应地应用横切关注点。
但它有一个限制。我们不能在“最终”类之间应用横切关注点(或方面),因为它们无法被覆盖,因此会导致运行时异常。
这同样适用于静态和最终方法。Spring 方面不能应用于它们,因为它们不能被覆盖。因此,Spring AOP 由于这些限制,仅支持方法执行连接点。
然而,AspectJ 在运行之前将横切关注点直接编织到实际代码中。与 Spring AOP 不同,它不需要子类化目标对象,因此也支持许多其他连接点。以下是支持的连接点的摘要:
连接点 | Spring AOP 支持 | 支持 AspectJ |
方法调用 | 不 | 是的 |
方法执行 | 是的 | 是的 |
构造函数调用 | 不 | 是的 |
构造函数执行 | 不 | 是的 |
静态初始化器执行 | 不 | 是的 |
对象初始化 | 不 | 是的 |
现场参考 | 不 | 是的 |
现场作业 | 不 | 是的 |
处理程序执行 | 不 | 是的 |
建议执行 | 不 | 是的 |
还值得注意的是,在 Spring AOP 中,方面并不应用于同一类中调用的方法。
这显然是因为当我们调用同一个类中的方法时,我们并没有调用 Spring AOP 提供的代理的方法。如果我们需要这个功能,那么我们必须在不同的bean中定义一个单独的方法,或者使用AspectJ。
3.5. 简单
Spring AOP 显然更简单,因为它没有在我们的构建过程之间引入任何额外的编译器或编织器。它使用运行时编织,因此它与我们通常的构建过程无缝集成。虽然它看起来很简单,但它只适用于由 Spring 管理的 bean。
但是,要使用 AspectJ,我们需要引入 AspectJ 编译器 (ajc) 并重新打包所有库(除非我们切换到编译后或加载时编织)。
当然,这比前者更复杂——因为它引入了 AspectJ Java 工具(其中包括编译器(ajc)、调试器(ajdb)、文档生成器(ajdoc)、程序结构浏览器(ajbrowser))需要与我们的 IDE 或构建工具集成。
3.6. 表现就性能而言,编译时编织比运行时编织快得多。Spring AOP是一个基于代理的框架,因此在应用程序启动时会创建代理。此外,每个方面还有更多的方法调用,这会对性能产生负面影响。
另一方面,AspectJ 在应用程序执行之前将方面编织到主代码中,因此与 Spring AOP 不同,没有额外的运行时开销。
由于这些原因,基准测试表明 AspectJ 几乎比 Spring AOP 快 8 到 35 倍。
4. 总结此快速表总结了 Spring AOP 和 AspectJ 之间的主要区别:
春季AOP | 方面J |
用纯Java实现 | 使用Java编程语言的扩展实现 |
无需单独的编译过程 | 需要 AspectJ 编译器 (ajc),除非设置了 LTW |
仅运行时编织可用 | 运行时编织不可用。支持编译时、编译后和加载时 Weaving |
功能较弱 – 仅支持方法级别编织 | 更强大——可以编织字段、方法、构造函数、静态初始化器、最终类/方法等…… |
只能在Spring容器管理的bean上实现 | 可以在所有域对象上实现 |
仅支持方法执行切入点 | 支持所有切入点 |
代理是由目标对象创建的,方面应用于这些代理 | 在执行应用程序之前(运行时之前),方面直接编织到代码中 |
比 AspectJ 慢很多 | 更好的性能 |
易于学习和应用 | 比 Spring AOP 相对复杂一些 |
5. 选择正确的框架如果我们分析本节中提出的所有论点,我们将开始理解,并不是一个框架就比另一个框架更好。
简而言之,选择很大程度上取决于我们的要求:
- 框架:如果应用程序没有使用 Spring 框架,那么我们别无选择,只能放弃使用 Spring AOP 的想法,因为它无法管理 Spring 容器范围之外的任何内容。但是,如果我们的应用程序完全使用 Spring 框架创建,那么我们可以使用 Spring AOP,因为它易于学习和应用
- 灵活性:鉴于有限的连接点支持,Spring AOP 不是一个完整的 AOP 解决方案,但它解决了程序员面临的最常见问题。尽管如果我们想要更深入地挖掘并最大限度地利用 AOP 并希望获得广泛的可用连接点的支持,那么 AspectJ 是您的选择
- 性能:如果我们使用有限的方面,那么性能差异很小。但有时应用程序具有超过数万个方面的情况。在这种情况下,我们不想使用运行时编织,因此最好选择 AspectJ。已知 AspectJ 比 Spring AOP 快 8 到 35 倍
- 两者的优点:这两个框架彼此完全兼容。我们总是可以尽可能利用 Spring AOP,并且仍然使用 AspectJ 来获得前者不支持的连接点的支持
六,结论在本文中,我们在几个关键领域分析了 Spring AOP 和 AspectJ。
我们比较了这两种 AOP 方法的灵活性以及它们与我们的应用程序的配合程度。