Skip to content

AOP 核心概念 (AOP Concepts)

首先,让我们定义一些核心的 AOP 概念和术语。这些术语并非 Spring 特有。遗憾的是,AOP 的术语并不直观,但如果 Spring 使用自己的术语,情况会变得更加混乱。

核心术语

  • 切面 (Aspect):横切多个类的关注点的模块化。事务管理是企业级 Java 应用中横切关注点的一个典型例子。在 Spring AOP 中,切面可以使用常规类(基于 Schema 的方式)或使用了 @Aspect 注解的常规类(@AspectJ 风格)来实现。
  • 连接点 (Join point):程序执行过程中的某个点,例如方法的执行或异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法的执行
  • 通知 (Advice):切面在此特定连接点上采取的动作。不同类型的通知包括“环绕(around)”、“前置(before)”和“后置(after)”通知。(通知类型将在后文讨论)。许多 AOP 框架(包括 Spring)将通知模型化为 拦截器 (Interceptor),并在连接点周围维护一个拦截器链。
  • 切入点 (Pointcut):匹配连接点的谓词(Predicate)。通知与切入点表达式相关联,并在切入点匹配的任何连接点上运行(例如,执行具有特定名称的方法)。切入点表达式匹配连接点是 AOP 的核心,Spring 默认使用 AspectJ 的切入点表达式语言。
  • 引入 (Introduction):代表一个类型声明额外的方法或字段。Spring AOP 允许你向任何被通知的对象引入新的接口(以及相应的实现)。例如,你可以使用引入使 Bean 实现 IsModified 接口,以简化缓存。(在 AspectJ 社区中,引入被称为“类型间声明 (Inter-type declaration)”)。
  • 目标对象 (Target object):被一个或多个切面所通知的对象。也称为“被通知(Advised)对象”。由于 Spring AOP 是通过运行时代理实现的,因此该对象总是一个 被代理 (Proxied) 的对象
  • AOP 代理 (AOP proxy):由 AOP 框架创建的对象,用于实现切面契约(执行通知方法等)。在 Spring 框架中,AOP 代理可以是 JDK 动态代理CGLIB 代理
  • 织入 (Weaving):将切面与其他应用类型或对象连接起来,以创建一个被通知的对象。这可以在编译时(例如使用 AspectJ 编译器)、类加载时或运行时完成。Spring AOP 与其他纯 Java AOP 框架一样,在 运行时 完成织入。

通知类型 (Advice Types)

Spring AOP 包含以下几种类型的通知:

  • 前置通知 (Before advice):在连接点之前运行,但无法阻止执行流程推进到连接点(除非它抛出异常)。
  • 后置返回通知 (After returning advice):在连接点正常完成后运行(例如,如果方法返回且未抛出异常)。
  • 异常通知 (After throwing advice):如果方法通过抛出异常退出,则运行该通知。
  • 后置最终通知 (After (finally) advice):无论连接点以何种方式退出(正常返回或异常流出),都会运行该通知。
  • 环绕通知 (Around advice):包围连接点(如方法调用)的通知。这是最强大的一种通知。环绕通知可以在方法调用前后执行自定义行为。它还负责选择是继续执行连接点,还是通过返回自己的返回值或抛出异常来“短路”被通知的方法执行。

通知的使用建议

环绕通知是最通用的通知类型。由于 Spring AOP(与 AspectJ 一样)提供了全方位的通知类型,我们建议你 优先使用能实现所需行为的最弱通知类型

例如,如果你只需要根据方法的返回值更新缓存,使用“后置返回通知”比使用“环绕通知”更好,尽管环绕通知也能完成同样的事情。使用最具体的通知类型可以提供更简单的编程模型,并减少出错的可能性。例如,你不需要在 JoinPoint 上手动调用 proceed() 方法(这是环绕通知必需的),因此也就不会忘记调用它。

所有通知参数都是静态类型的,因此你可以直接使用适当类型的参数(例如方法执行的返回值类型),而不是使用 Object 数组。


补充教学

1. 深度解析:连接点 (Join point) vs. 切入点 (Pointcut)

初学者最容易混淆这两个概念。你可以把它们想象成:

  • 连接点 (Join point):这是“所有可能的点”。在 Spring AOP 里,每一个方法的执行(Service 层的 save, update, find 等)都是一个连接点。
  • 切入点 (Pointcut):这是“你选中的点”。是一组符合你筛选条件的连接点集合。
  • 比喻:所有的餐厅都是“连接点”,但你定义“米其林三星餐厅”这个规则就是“切入点”。

2. 为什么 Spring 只有“方法执行”这一种连接点?

AspectJ 支持字段访问(Field access)、构造函数调用等极其精细的连接点。但 Spring AOP 基于 JDK/CGLIB 代理

  • 局限性:代理对象只能拦截通过方法进行的调用。
  • 优势:这覆盖了 95% 以上的企业级开发场景(如事务、日志、安全),且不需要修改类加载器或使用特殊的编译器,性能与复杂度的平衡极佳。

3. 通知的执行顺序(Spring 5.x+)

当同一个连接点上有多个通知时,它们的执行顺序如下(以环绕通知包含其他通知为例):

  1. 环绕逻辑(前)
  2. 前置通知 (@Before)
  3. 目标方法执行
  4. 后置返回通知 (@AfterReturning)异常通知 (@AfterThrowing)
  5. 后置最终通知 (@After)
  6. 环绕逻辑(后)

4. proceed():环绕通知的灵魂

在环绕通知中,ProceedingJoinPoint.proceed() 的调用至关重要。

  • 漏调用:目标方法将永远不会执行(这是拦截恶意请求的常用手段)。
  • 多次调用:目标方法会执行多次(这在实现重试逻辑(Retry Mechanism)时非常有用)。

5. 织入 (Weaving) 的时机

  • 运行时织入 (Spring AOP):通过动态代理在内存中创建一个子类或实现类。无需特殊构建步骤,易于调试。
  • 编译时织入 (AspectJ):使用 ajc 编译器直接修改 .class 文件。性能最强,能拦截 private 方法。
  • 加载时织入 (LTW):在类加载器加载类时进行字节码增强。

Based on Spring Framework.