以下是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后置(计算耗时)

关键说明

  1. 耗时记录原理
    通过 @Around 通知在目标方法执行前后记录时间戳(System.currentTimeMillis()),差值即为方法执行耗时,这是 AOP 记录接口耗时的标准实现方式。

  2. 各通知的核心作用

    • @Around:唯一能获取方法执行前后完整生命周期的通知,适合耗时统计、权限控制等。
    • @Before:适合方法执行前的参数校验、日志打印。
    • @AfterReturning:适合记录方法返回结果(如缓存写入)。
    • @AfterThrowing:适合异常监控、告警通知。
    • @After:适合资源释放(如关闭连接、清理临时文件)。
  3. 顺序关键点

    • @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不存在");
    }
}

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐