Spring AOP:从 注解配置 到 通知执行顺序 详解

2 阅读7分钟

AOP 是什么

AOP 的中文和英文全称分别是:面向切面编程Aspect Oriented Programming
它是将复杂重复的无关业务的逻辑从代码中“切出来”进行统一封装,进行代码的解耦。极大地提高了业务代码的清晰度,使项目更有调理,代码更强壮。


AOP 术语 & 增强类型

AOP 术语

  • 切面(Aspect :一般单独作为一个类,他是 PointcutJointpoint 的集合,在AOP中表示为在哪干和干什么集合
  • 连接点(Jointpoint :方法中可以插入 AOP 的一点。即使用 Spring AOP 框架采取操作的实际位置
  • 切入点(Pointcut :多个 Jointpoint 的集合,表示在哪里执行 Advice 增强代码。
  • 增强/通知(Advice :具体的增强的代码操作,表示做了什么
  • 引入(inter-type declaration :引用可以让我们向类添加新的字段和方法
  • 织入(Weaving创建一个被增强对象,这些可以在编译时类加载时运行时完成

AOP 增强类型

  • 前置增强(Before advice :在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
  • 后置增强(After returning advice :在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • 异常增强(After throwing advice :在方法抛出异常退出时执行的通知。
  • 最终增强(After (finally) advice :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
  • 环绕增强(Around Advice:环绕通知可以在方法调用前后完成自定义的行为。

使用方式

1. AspectJ 注解配置方式

为了使用这个注解,有两种配置的方式,分别为 xml 和 AspectJ 。

(1)XML 配置方式 :

xml 配置已经过时:

<aop:aspectj-autoproxy/>

(2)注解 @EnableAspectJAutoProxy 配置方式 :

注意:@Aspect 注解的类,必须被 Spring 容器扫描到(即必须是 Bean),否则 AOP 不生效

@Configuration //配置类注解
@EnableAspectJAutoProxy //AspectJ配置注解
public class Config {

}

(3)配置完后 AspectJ 的使用方式 :

在使用前首先要注意的是

  1. 配置完毕后,所有写了 @AspectBean 都会被 Spring 当作成一个 Aspect 切面。
  2. 另外只有被 IoC 接管的 Bean (三种IoC的配置方式,xml 配置的,javaConfig 配置的,注解配置的)才能进行 @Aspect 注解,所以要用@Component等注解将其先修饰成一个 Bean 。 或者xml配置和javaConfig配置的一系列操作。

例如如下代码:

// 有效的AOP配置类
@Aspect //(定义切面)
@Component //(将其定义为 Bean 交给 SpringIoC 管理)
public class MyAspect { 

//.... 

}

2. Pointcut 配置

写在Aspect配置类中

(1) execution

@Pointcut("execution(* testExecution(..))")
public void anyTestMethod()
{
//...
}
  • 这是 AspectJ 的核心,也是 Spring AOP 最常用的方式。
  • 它通过方法签名(返回值、包路径、类名、方法名、参数)来匹配。

(2) within

@Pointcut("within(ric.study.demo.aop.svc..*)")
public void inSvcLayer() 
{
//...
}
  • 它只关心类在哪个包下,不关心方法叫什么。一行代码就能覆盖整个模块。

(3) @annotation

@Pointcut("@annotation(ric.study.demo.aop.HaveAop)")
public void withAnnotation()
{
//...
}
  • 它可以进行定义注解,之后在使用的过程中出现上面代码中的“ @HaveAop ”时,就会进行拦截,执行这个方法对应的增强 Advice 。
  • 它匹配带有特定注解的方法。这是目前业务开发中最推荐的方式。

(4) Bean

@Pointcut("bean(testController)")
public void inControllerLayer()
{
//...
}
  • 只对某个特定的 Bean 做特殊的切面增强

四个方式的总结

  1. 首选 @annotation
    对于具体的业务功能,这是最好的选择。虽然需要加注解,但它让代码的可读性和维护性达到了最高,一看代码就知道这个方法被 AOP 增强了。“颗粒度”更细execution 通常匹配一大类方法(比如所有 service 包),而 @annotation 可以精确到某一个具体的方法。
  2. 次选 execution + within
    对于非业务逻辑的通用技术组件,使用 execution 或 within 匹配包路径。这样我们就不需要在代码里到处加注解,可以保持代码整洁。
  3. 尽量少用 bean

3. 配置 Advice 增强

首先要注意的是:Aspect 类应该遵守单一职责原则,不要把所有的Advice配置全部写在一个Aspect类里面。

(1)@Before 前置增强

@Before("servicePointcut()") 
public void doBeforeAdvice() {
    System.out.println("[前置增强] 目标方法即将执行"); 
    }
}
  • 在执行方法前运行

(2)@AfterReturning 后置增强

@AfterReturning(pointcut = "servicePointcut()", returning = "result") 
public void doAfterReturningAdvice(Object result) {
    System.out.println("[后置增强] 方法执行成功,返回值: " + result); 
}
  • 在执行方法成功后运行(无异常的情况下,有异常不执行)

(3)@AfterThrowing 异常增强

@AfterThrowing(pointcut = "servicePointcut()", throwing = "ex") 
public void doAfterThrowingAdvice(Exception ex) {
    System.out.println("[异常增强] 方法抛出异常: " + ex.getMessage()); 
}
  • 在方法出现异常时运行

(4)@After 最终增强

@After("servicePointcut()") 
public void doAfterAdvice() {
    System.out.println("[最终增强] 方法执行结束(无论成功或异常)"); 
}
  • 无论是否异常,在方法执行结束后运行

(5)@Around 环绕增强

@Around("servicePointcut()") 
public Object doAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("[环绕增强] 方法执行前");
    Object result = joinPoint.proceed(); // 执行目标方法
    System.out.println("[环绕增强] 方法执行后");
    return result; 
}

关于这五个 Advice 增强方法的执行顺序详解:

情况一:正常执行流程 (无异常):
  1. @Around (环绕前)
    内置的环绕增强的代码会首先执行,直到遇到 joinPoint.proceed() 这一行,让流程进入下一环节。
  2. @Before (前置增强)
    一旦 proceed() 被调用,流程就进入了前置增强。通常进行方法的准备工作
  3. 目标方法执行
    前置增强执行结束之后,真正的业务逻辑(比如 Service 方法)开始执行。
  4. @AfterReturning (返回增强)
    如果目标方法成功执行并返回了结果,流程就会触发返回后增强。这是处理返回值、记录成功日志的地方。因为如果方法抛出了异常,这一步会被跳过
  5. @Around (环绕后)
    在 @AfterReturning 之后,流程会回到 proceed() 方法调用的下一行。环绕增强可以继续处理返回值,或者计算方法的执行耗时。
  6. @After (最终增强)
    这是整个流程的“收尾”工作。无论前面目标方法执行成功还是失败,最终增强都一定会执行。常用于释放资源。
情况二:执行流程出现异常(抛出异常):
  1. @Around (环绕前)
    流程同样从环绕增强开始。
  2. @Before (前置增强)
    调用 proceed() 后,执行前置增强。
  3. 目标方法
    业务逻辑开始执行,但里面的方法在执行时抛出了一个异常
  4. @AfterThrowing (异常增强)
    一旦目标方法抛出异常,流程会立即中断,并跳转到异常增强。这里是记录错误日志、发送告警通知的位置。此时 @AfterReturning 和 " @Aroundproceed() " 后的方法不执行(因为抛出异常了,方法执行失败)。
  5. @After (最终增强)
    和正常流程一样,无论是否发生异常,最终增强都一定会执行,用于清理资源。

流程表格:

增强类型正常流程异常流程核心特点
@Around (proceed前)执行执行功能最强,可控制方法执行
@Before执行执行在目标方法之前,做准备工作
目标方法执行无异常出现异常!
@AfterReturning执行不执行仅在方法成功返回后执行
@AfterThrowing不执行执行仅在方法抛出异常后执行
@Around (proceed后)执行不执行功能最强,可控制方法执行
@After执行执行无论成败,最终都会执行

通过对比可以看出:

  1. @AfterReturning@Around(中的后置方法) 是同时出现(无异常)。
  2. @AfterThrowing 是和他们对立出现(有异常)。

结语

本文结束
如果这篇文章帮你理清了思路,我也感到很开心。
这也同时是我的学习笔记,能帮到别人我不胜感谢,若哪里出错希望指出,同样不胜感激!

以上,@Aroaku