选择 AOP 声明风格 (Choosing which AOP Declaration Style to Use)
一旦你决定使用切面是实现特定要求的最佳方法,你该如何在以下选项中做出选择:Spring AOP 还是全功能的 AspectJ?是使用 Aspect 语言(代码风格)、@AspectJ 注解风格,还是 Spring XML 风格?这些决策受应用需求、开发工具以及团队对 AOP 熟悉程度等多种因素影响。
Spring AOP 还是全功能的 AspectJ?
使用能工作的最简单方案。 Spring AOP 比使用全功能的 AspectJ 更简单,因为它不需要在开发和构建过程中引入 AspectJ 编译器/织入器。如果你只需要通知 Spring Bean 上的操作执行,Spring AOP 是正确选择。
如果你需要通知不受 Spring 容器管理的对象(通常是领域对象/Domain Objects),则需要使用 AspectJ。此外,如果你希望通知除简单方法执行以外的连接点(例如:字段的 get 或 set 连接点等),也需要使用 AspectJ。
当你使用 AspectJ 时,你可以选择 AspectJ 语言语法(也称为“代码风格”)或 @AspectJ 注解风格。如果切面在你的设计中扮演重要角色,并且你能够使用 Eclipse 的 AspectJ Development Tools (AJDT) 插件,那么 AspectJ 语言语法是首选。它更干净、更简单,因为该语言是专门为编写切面而设计的。如果你不使用 Eclipse,或者应用中只有少数切面且不占主导地位,你可能更倾向于考虑使用 @AspectJ 风格,在 IDE 中坚持使用常规的 Java 编译,并在构建脚本中添加切面织入阶段。
@AspectJ 还是 XML 风格?
如果你选择了使用 Spring AOP,那么你可以在 @AspectJ 或 XML 风格之间进行选择。这其中有各种权衡。
XML 风格对于现有的 Spring 用户可能最为熟悉,且它由真正的 POJO 支持。当使用 AOP 作为配置企业服务的工具时,XML 是一个不错的选择(一个好的判断标准是:你是否认为切入点表达式是配置的一部分,且你可能希望独立于代码进行更改)。使用 XML 风格,从配置中可以很清晰地看到系统中存在哪些切面。
XML 风格有两个缺点。首先,它没有将需求的实现完全封装在一个地方。DRY(Don't Repeat Yourself)原则指出,系统中任何知识点都应该有单一、清晰且权威的表现形式。使用 XML 风格时,如何实现需求的知识被分散在后台 Bean 类和 XML 配置文件中。而使用 @AspectJ 风格时,这些信息被封装在一个单一模块中:切面(Aspect)。其次,XML 风格在表达能力上略逊于 @AspectJ 风格:它仅支持“单例”实例化模型,且无法组合 XML 中声明的命名切入点。
例如,在 @AspectJ 风格中,你可以这样写:
@Pointcut("execution(* get*())")
public void propertyAccess() {}
@Pointcut("execution(com.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}
@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}@Pointcut("execution(* get*())")
fun propertyAccess() {}
@Pointcut("execution(com.xyz.Account+ *(..))")
fun operationReturningAnAccount() {}
@Pointcut("propertyAccess() && operationReturningAnAccount()")
fun accountPropertyAccess() {}在 XML 风格中,你可以声明前两个切入点,但无法通过组合它们来定义 accountPropertyAccess 切入点。
@AspectJ 风格支持更多的实例化模型和更丰富的切入点组合。它的优势在于将切面作为一个模块化单元。此外,@AspectJ 切面既能被 Spring AOP 理解,也能被 AspectJ 理解。因此,如果你以后决定需要 AspectJ 的功能来实现额外需求,可以轻松迁移到传统的 AspectJ 设置。
总的来说,对于除了简单配置企业服务以外的自定义切面,Spring 团队更倾向于使用 @AspectJ 风格。
补充教学
1. 决策矩阵:Spring AOP vs AspectJ
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 能力范围 | 仅支持 Spring Bean 的方法执行 | 支持任何 Java 对象,包括字段访问、构造函数、类初始化等 |
| 织入时机 | 运行时 (Runtime) 通过代理对象织入 | 编译时 (CTW)、编译后或加载时 (LTW) 织入 |
| 复杂性 | 低(纯 Java,无额外构建步骤) | 高(需要 ajc 编译器或相应的 Maven/Gradle 插件) |
| 性能 | 略低(代理调用开销) | 极高(字节码级织入,无额外调用栈) |
| 推荐场景 | 大多数 Web 应用中的日志、事务、权限检查 | 核心领域模型的复杂逻辑、全局性能监控、字段拦截 |
2. 为什么 @AspectJ 是现代开发的主流?
- 高内聚(Encapsulation):逻辑和规则在一个
.java文件里,阅读和修改时不需要在两个文件间跳跃。 - 类型安全:在代码里写切面,IDE 可以提供自动补全和重构支持(虽然字段名匹配仍是字符串,但比 XML 强)。
- Spring Boot 亲和性:Spring Boot 侧重于 Java Config 和注解。
- 向上兼容:正如文档所言,它是从 Spring AOP 迈向 Full AspectJ 的“无痛桥梁”。
3. XML AOP 还是“一无是处”吗?
不,它在特定场景下依然非常强大:
- 集中策略控制:如果你想在不触碰源代码的情况下,通过修改一个配置文件就停用或开启整个系统的某一类切面(例如全局关闭所有 Service 的审计日志),XML 依然是最佳选择。
- 第三方库集成:如果你没有第三方库的源码,无法在上面加注释,通过 XML 来织入通知是唯一可行且优雅的方法。
4. 理解 DRY 原则在 AOP 中的体现
- XML 的痛点:如果你修改了通知方法的名称,你必须记得去 XML 里修改
method="newMethodName"。这违反了 DRY。 - @AspectJ 的优雅:代码即声明,修改方法名,IDE 的重构工具会自动帮你更新所有相关的切面逻辑。
5. 建议:混合使用的艺术
在大型项目中,通常会混合使用:
- 基础架构层(事务、安全性):往往使用 Spring 自带的命名空间(本质是 XML/Advisor 风格的封装)。
- 业务逻辑层(重试、日志、缓存):推荐使用全注解的 @AspectJ。
- 特殊需求(监控第三方 Jar 包):使用 XML 配置。