Spring AOP 通知执行执行顺序完整案例、以及执行时机
·
以下是Spring AOP 通知执行执行顺序完整案例,通过 @Around 通知记录方法执行时间,同时展示所有通知注解 的执行时机:
package com.example.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* AOP切面:展示所有通知执行顺序,并记录方法耗时
*/
@Aspect
@Component
public class TimeLogAspect {
// 切点:匹配UserService中的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void servicePointcut() {}
// 前置通知
@Before("servicePointcut()")
public void beforeAdvice() {
System.out.println("[@Before] 前置通知:方法即将执行");
}
// 后置返回通知
@AfterReturning(pointcut = "servicePointcut()", returning = "result")
public void afterReturningAdvice(Object result) {
System.out.println("[@AfterReturning] 返回后通知:方法正常返回,结果=" + result);
}
// 后置异常通知
@AfterThrowing(pointcut = "servicePointcut()", throwing = "ex")
public void afterThrowingAdvice(Exception ex) {
System.out.println("[@AfterThrowing] 异常通知:方法抛出异常,信息=" + ex.getMessage());
}
// 最终通知
@After("servicePointcut()")
public void afterAdvice() {
System.out.println("[@After] 最终通知:方法执行结束(无论成功或失败)");
}
// 环绕通知(核心:记录耗时)
@Around("servicePointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 环绕通知前置部分:记录开始时间和参数
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("\n[@Around] 环绕通知开始:方法=" + methodName + ",参数=" + Arrays.toString(args));
Object result = null;
try {
// 2. 执行目标方法(这是核心,必须调用proceed())
result = joinPoint.proceed();
// 3. 目标方法正常返回后(相当于@AfterReturning的前置)
return result;
} catch (Exception e) {
// 4. 目标方法抛出异常后(相当于@AfterThrowing的前置)
throw e;
} finally {
// 5. 环绕通知后置部分:计算耗时
long endTime = System.currentTimeMillis();
long costTime = endTime - startTime;
System.out.println("[@Around] 环绕通知结束:方法=" + methodName + ",耗时=" + costTime + "ms");
}
}
}
执行结果与顺序分析
1. 目标方法正常执行(getUser)时的输出:
===== 测试正常执行的方法 =====
[@Around] 环绕通知开始:方法=getUser,参数=[1001]
[@Before] 前置通知:方法即将执行
[目标方法] 执行getUser(1001):查询用户信息
[@AfterReturning] 返回后通知:方法正常返回,结果=用户信息-1001
[@After] 最终通知:方法执行结束(无论成功或失败)
[@Around] 环绕通知结束:方法=getUser,耗时=105ms // 包含模拟的100ms耗时
执行顺序:@Around前置 → @Before → 目标方法 → @AfterReturning → @After → @Around后置(计算耗时)
2. 目标方法抛出异常(deleteUser)时的输出:
===== 测试抛出异常的方法 =====
[@Around] 环绕通知开始:方法=deleteUser,参数=[1002]
[@Before] 前置通知:方法即将执行
[目标方法] 执行deleteUser(1002):尝试删除用户
[@AfterThrowing] 异常通知:方法抛出异常,信息=删除失败:用户ID不存在
[@After] 最终通知:方法执行结束(无论成功或失败)
[@Around] 环绕通知结束:方法=deleteUser,耗时=53ms // 包含模拟的50ms耗时
执行顺序:@Around前置 → @Before → 目标方法(抛异常) → @AfterThrowing → @After → @Around后置(计算耗时)
关键说明
-
耗时记录原理:
通过@Around通知在目标方法执行前后记录时间戳(System.currentTimeMillis()),差值即为方法执行耗时,这是 AOP 记录接口耗时的标准实现方式。 -
各通知的核心作用:
@Around:唯一能获取方法执行前后完整生命周期的通知,适合耗时统计、权限控制等。@Before:适合方法执行前的参数校验、日志打印。@AfterReturning:适合记录方法返回结果(如缓存写入)。@AfterThrowing:适合异常监控、告警通知。@After:适合资源释放(如关闭连接、清理临时文件)。
-
顺序关键点:
@Around的前置部分最早执行,后置部分最晚执行(包裹所有通知和目标方法)。@After无论方法成功或失败都会执行,确保最终清理逻辑生效。@AfterReturning和@AfterThrowing互斥,仅在对应场景下执行。
通过这个案例,可以清晰理解 AOP 各通知的执行时机,并掌握如何利用 @Around 实现接口耗时统计功能。
其余代码补全:
package com.example;
import com.example.service.UserService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class AopDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(AopDemoApplication.class, args);
UserService userService = context.getBean(UserService.class);
// 测试正常执行的方法
System.out.println("===== 测试正常执行的方法 =====");
userService.getUser("1001");
// 测试抛出异常的方法
System.out.println("\n===== 测试抛出异常的方法 =====");
try {
userService.deleteUser("1002");
} catch (Exception e) {
// 捕获异常,避免程序终止
}
}
}
package com.example.service;
import org.springframework.stereotype.Service;
/**
* 目标服务类:被AOP增强的类
*/
@Service
public class UserService {
// 正常执行的方法
public String getUser(String userId) {
try {
// 模拟方法执行耗时(100ms)
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("[目标方法] 执行getUser(" + userId + "):查询用户信息");
return "用户信息-" + userId;
}
// 会抛出异常的方法
public void deleteUser(String userId) {
try {
// 模拟方法执行耗时(50ms)
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("[目标方法] 执行deleteUser(" + userId + "):尝试删除用户");
// 模拟抛出异常
throw new RuntimeException("删除失败:用户ID不存在");
}
}
更多推荐

所有评论(0)