Spring AOP(面向切面编程)

ℹ️定位说明

Spring 系列第 3 篇。核心回答:日志/事务这类横切逻辑怎么不侵入业务——九部分从问题提出到核心概念/术语、实现原理(JDK 动态代理 vs CGLIB)、五种通知类型、切入点表达式、实际应用、执行顺序与最佳实践。前置:动态代理

第一部分:问题的提出

一、传统方式的困境

想象一个场景:

// 传统方式:业务代码中混入大量横切关注点
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping
    public void createUser(User user) {
        // 日志:记录请求
        System.out.println("创建用户请求:" + user);

        // 权限检查
        if (!hasPermission()) {
            throw new SecurityException("无权限");
        }

        // 性能监控:记录开始时间
        long startTime = System.currentTimeMillis();

        try {
            // 业务逻辑
            userService.createUser(user);

            // 日志:记录成功
            System.out.println("用户创建成功");
        } catch (Exception e) {
            // 异常处理
            System.out.println("用户创建失败:" + e.getMessage());
            throw e;
        } finally {
            // 性能监控:记录耗时
            long endTime = System.currentTimeMillis();
            System.out.println("耗时:" + (endTime - startTime) + "ms");
        }
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable int id) {
        // 又要重复写一遍日志、权限、性能监控的代码...
        System.out.println("查询用户请求:" + id);
        if (!hasPermission()) {
            throw new SecurityException("无权限");
        }
        long startTime = System.currentTimeMillis();
        try {
            User user = userService.getUser(id);
            System.out.println("用户查询成功");
            return user;
        } catch (Exception e) {
            System.out.println("用户查询失败:" + e.getMessage());
            throw e;
        } finally {
            long endTime = System.currentTimeMillis();
            System.out.println("耗时:" + (endTime - startTime) + "ms");
        }
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable int id) {
        // 又要重复写一遍...
    }
}

这种方式有什么问题?

  1. 代码重复 - 日志、权限、性能监控代码在每个方法中重复
  2. 职责混乱 - 业务逻辑和增强逻辑混在一起
  3. 难以维护 - 修改增强逻辑需要改动所有方法
  4. 难以测试 - 无法单独测试业务逻辑
  5. 耦合度高 - 业务代码依赖于增强逻辑

二、理想的解决方案

我们希望能这样写:

// 理想方式:业务代码只关注业务逻辑
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping
    public void createUser(User user) {
        // 只写业务逻辑,增强逻辑由AOP自动处理
        userService.createUser(user);
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable int id) {
        // 只写业务逻辑
        return userService.getUser(id);
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable int id) {
        // 只写业务逻辑
        userService.deleteUser(id);
    }
}

// 通过AOP自动处理日志、权限、性能监控等

这就是AOP要解决的问题:在不改变原始代码的前提下,为方法自动织入增强逻辑。


第二部分:AOP的核心概念

三、什么是AOP?

3.1 AOP的定义

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,通过在程序运行时动态地将代码切入到指定的位置,实现对横切关注点的统一处理。

┌──────────────────────────────────────────┐
│         AOP的本质                         │
├──────────────────────────────────────────┤
│                                          │
│  问题:                                   │
│  ├─ 日志、权限、事务等逻辑                │
│  ├─ 与具体业务无关                        │
│  ├─ 但所有方法都需要                      │
│  └─ 导致代码重复、耦合高                  │
│                                          │
│  解决方案:                               │
│  ├─ 将这些逻辑抽离出来                    │
│  ├─ 统一成"切面"                         │
│  ├─ 通过代理模式织入                      │
│  └─ 实现"非侵入式编程"                   │
│                                          │
│  核心思想:                               │
│  └─ "在什么时候,在什么位置,                │
│     做什么事情"                          │
│                                          │
└──────────────────────────────────────────┘

3.2 AOP与面向对象编程(OOP)的关系

┌──────────────────────────────────────────┐
│      AOP与OOP的关系                       │
├──────────────────────────────────────────┤
│                                          │
│  OOP(面向对象编程)                      │
│  ├─ 纵向:类的继承、多态                  │
│  ├─ 解决:代码复用、结构清晰              │
│  └─ 特点:纵向扩展                        │
│                                          │
│  AOP(面向切面编程)                      │
│  ├─ 横向:切面的织入                      │
│  ├─ 解决:横切关注点                      │
│  └─ 特点:横向扩展                        │
│                                          │
│  关系:                                   │
│  └─ AOP是OOP的补充和扩展                 │
│     OOP处理纵向,AOP处理横向             │
│                                          │
└──────────────────────────────────────────┘

3.3 AOP的核心优势

┌──────────────────────────────────────────┐
│         AOP的核心优势                     │
├──────────────────────────────────────────┤
│                                          │
│  1. 解耦                                  │
│     ├─ 业务逻辑和增强逻辑分离            │
│     ├─ 易于维护和扩展                    │
│     └─ 符合单一职责原则                  │
│                                          │
│  2. 非侵入式编程                          │
│     ├─ 不改动原始代码                    │
│     ├─ 通过配置织入增强逻辑              │
│     └─ 想加就加,想撕就撕                │
│                                          │
│  3. 代码复用                              │
│     ├─ 一个切面适用多个方法              │
│     ├─ 减少代码重复                      │
│     └─ 易于维护                          │
│                                          │
│  4. 灵活性                                │
│     ├─ 支持多个切面组合                  │
│     ├─ 支持切面顺序控制                  │
│     └─ 支持条件织入                      │
│                                          │
│  5. 可维护性                              │
│     ├─ 增强逻辑集中管理                  │
│     ├─ 易于修改和扩展                    │
│     └─ 代码更清晰                        │
│                                          │
└──────────────────────────────────────────┘

第三部分:AOP的核心术语

四、AOP的专业术语

4.1 术语解释

┌──────────────────────────────────────────┐
│         AOP的核心术语                     │
├──────────────────────────────────────────┤
│                                          │
│  连接点(JoinPoint)                      │
│  ├─ 定义:所有能被增强的位置              │
│  ├─ 包括:方法、构造器、字段              │
│  └─ 例子:UserService的所有方法          │
│                                          │
│  切入点(Pointcut)                       │
│  ├─ 定义:我们实际选中的位置              │
│  ├─ 包括:特定的方法、包下的所有方法      │
│  └─ 例子:UserService.createUser()       │
│                                          │
│  通知(Advice)                           │
│  ├─ 定义:真正的增强逻辑                  │
│  ├─ 包括:日志、权限、事务等              │
│  └─ 例子:打印日志的代码                  │
│                                          │
│  切面(Aspect)                           │
│  ├─ 定义:通知+切入点的组合               │
│  ├─ 包括:在哪里、什么时候、做什么        │
│  └─ 例子:日志切面                        │
│                                          │
│  目标对象(Target)                       │
│  ├─ 定义:被增强的原始对象                │
│  ├─ 包括:UserService实例                │
│  └─ 特点:不知道自己被增强了              │
│                                          │
│  代理对象(Proxy)                        │
│  ├─ 定义:加完增强的对象                  │
│  ├─ 包括:由Spring创建的代理类实例       │
│  └─ 特点:对外使用的是这个对象            │
│                                          │
│  织入(Weaving)                          │
│  ├─ 定义:将增强逻辑应用到目标的过程      │
│  ├─ 包括:编译期、类加载期、运行期        │
│  └─ Spring:运行期织入                    │
│                                          │
└──────────────────────────────────────────┘

4.2 术语类比

┌──────────────────────────────────────────┐
│    AOP术语与"加特效"的类比                │
├──────────────────────────────────────────┤
│                                          │
│  术语              现实类比                │
│  ─────────────────────────────────────  │
│  连接点            能加特效的动作          │
│                   (跳跃、攻击)          │
│                                          │
│  切入点            选中"攻击"动作          │
│                   加特效                  │
│                                          │
│  通知              加特效本身              │
│                   (发光、爆炸)          │
│                                          │
│  切面              发光特效应用到          │
│                   攻击动作上              │
│                                          │
│  目标对象          没有特效的              │
│                   普通角色                │
│                                          │
│  代理对象          看上去很牛的            │
│                   角色                    │
│                                          │
│  织入              给角色加特效            │
│                   的过程                  │
│                                          │
└──────────────────────────────────────────┘

第四部分:AOP的实现原理

五、Spring AOP的实现方式

5.1 两种代理方式

┌──────────────────────────────────────────┐
│      Spring AOP的两种代理方式             │
├──────────────────────────────────────────┤
│                                          │
│  JDK动态代理                              │
│  ├─ 原理:基于接口实现                    │
│  ├─ 前提:目标类必须实现接口              │
│  ├─ 优点:原生支持,无需依赖              │
│  ├─ 缺点:只能代理接口方法                │
│  └─ 性能:反射调用,性能一般              │
│                                          │
│  CGLIB动态代理                            │
│  ├─ 原理:通过继承+字节码增强             │
│  ├─ 前提:目标类可以没有接口              │
│  ├─ 优点:支持代理类,灵活性强            │
│  ├─ 缺点:需要CGLIB依赖                   │
│  └─ 性能:字节码调用,性能较好            │
│                                          │
│  Spring的选择策略:                       │
│  ├─ Spring Framework:优先JDK            │
│  └─ Spring Boot 2.x+:优先CGLIB          │
│                                          │
└──────────────────────────────────────────┘

5.2 JDK动态代理实现AOP

// JDK动态代理实现AOP

// 1. 定义业务接口
public interface UserService {
    void createUser(User user);
    User getUser(int id);
}

// 2. 实现业务接口
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void createUser(User user) {
        System.out.println("创建用户:" + user.getName());
    }

    @Override
    public User getUser(int id) {
        System.out.println("查询用户:" + id);
        return new User(id, "Tom");
    }
}

// 3. 创建代理对象
public class JDKProxyAOPDemo {
    public static void main(String[] args) {
        // 创建目标对象
        UserService target = new UserServiceImpl();

        // 创建代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
            UserService.class.getClassLoader(),
            new Class[]{UserService.class},
            (proxyObj, method, args) -> {
                // 前置增强
                System.out.println("=== 方法调用前 ===");
                System.out.println("方法名:" + method.getName());
                System.out.println("参数:" + Arrays.toString(args));

                // 调用原方法
                long startTime = System.currentTimeMillis();
                Object result = method.invoke(target, args);
                long endTime = System.currentTimeMillis();

                // 后置增强
                System.out.println("=== 方法调用后 ===");
                System.out.println("耗时:" + (endTime - startTime) + "ms");
                System.out.println("返回值:" + result);

                return result;
            }
        );

        // 调用代理对象的方法
        proxy.createUser(new User(1, "Tom"));
        User user = proxy.getUser(1);
    }
}

5.3 CGLIB动态代理实现AOP

// CGLIB动态代理实现AOP

// 1. 定义业务类(无需接口)
@Service
public class UserService {
    public void createUser(User user) {
        System.out.println("创建用户:" + user.getName());
    }

    public User getUser(int id) {
        System.out.println("查询用户:" + id);
        return new User(id, "Tom");
    }
}

// 2. 创建代理对象
public class CGLIBProxyAOPDemo {
    public static void main(String[] args) {
        // 创建目标对象
        UserService target = new UserService();

        // 创建Enhancer
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
            // 前置增强
            System.out.println("=== 方法调用前 ===");
            System.out.println("方法名:" + method.getName());
            System.out.println("参数:" + Arrays.toString(args));

            // 调用原方法
            long startTime = System.currentTimeMillis();
            Object result = proxy.invokeSuper(obj, args);
            long endTime = System.currentTimeMillis();

            // 后置增强
            System.out.println("=== 方法调用后 ===");
            System.out.println("耗时:" + (endTime - startTime) + "ms");
            System.out.println("返回值:" + result);

            return result;
        });

        // 创建代理对象
        UserService proxy = (UserService) enhancer.create();

        // 调用代理对象的方法
        proxy.createUser(new User(1, "Tom"));
        User user = proxy.getUser(1);
    }
}

第五部分:AOP的通知类型

六、五种通知类型

6.1 通知类型概览

┌──────────────────────────────────────────┐
│         AOP的五种通知类型                 │
├──────────────────────────────────────────┤
│                                          │
│  @Before(前置通知)                      │
│  ├─ 执行时机:方法执行前                  │
│  ├─ 用途:权限检查、参数验证              │
│  └─ 特点:无法获取返回值                  │
│                                          │
│  @After(后置通知)                       │
│  ├─ 执行时机:方法执行后(无论异常)      │
│  ├─ 用途:资源释放、日志记录              │
│  └─ 特点:总是执行                        │
│                                          │
│  @AfterReturning(返回通知)              │
│  ├─ 执行时机:方法成功返回后              │
│  ├─ 用途:处理返回值、缓存                │
│  └─ 特点:可以获取返回值                  │
│                                          │
│  @AfterThrowing(异常通知)               │
│  ├─ 执行时机:方法抛异常时                │
│  ├─ 用途:异常处理、告警                  │
│  └─ 特点:可以获取异常信息                │
│                                          │
│  @Around(环绕通知)                      │
│  ├─ 执行时机:包裹整个方法执行            │
│  ├─ 用途:性能监控、事务管理              │
│  └─ 特点:最灵活,能实现所有通知效果      │
│                                          │
└──────────────────────────────────────────┘

6.2 通知执行顺序

┌──────────────────────────────────────────┐
│         通知的执行顺序                    │
├──────────────────────────────────────────┤
│                                          │
│  正常执行流程:                           │
│  ├─ @Before(前置)                      │
│  ├─ 执行原方法                           │
│  ├─ @AfterReturning(返回)              │
│  ├─ @After(后置)                       │
│  └─ 返回结果                             │
│                                          │
│  异常执行流程:                           │
│  ├─ @Before(前置)                      │
│  ├─ 执行原方法(抛异常)                  │
│  ├─ @AfterThrowing(异常)               │
│  ├─ @After(后置)                       │
│  └─ 抛出异常                             │
│                                          │
│  @Around的位置:                          │
│  └─ 包裹整个流程,最外层                  │
│                                          │
└──────────────────────────────────────────┘

6.3 各通知类型的实现

// 各通知类型的实现

@Aspect
@Component
public class UserServiceAspect {

    // 1. 前置通知:方法执行前
    @Before("execution(* com.example.service.UserService.*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        System.out.println("=== 前置通知 ===");
        System.out.println("方法名:" + joinPoint.getSignature().getName());
        System.out.println("参数:" + Arrays.toString(joinPoint.getArgs()));
    }

    // 2. 后置通知:方法执行后(无论异常)
    @After("execution(* com.example.service.UserService.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("=== 后置通知 ===");
        System.out.println("方法执行完成");
    }

    // 3. 返回通知:方法成功返回后
    @AfterReturning(
        pointcut = "execution(* com.example.service.UserService.*(..))",
        returning = "result"
    )
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        System.out.println("=== 返回通知 ===");
        System.out.println("返回值:" + result);
    }

    // 4. 异常通知:方法抛异常时
    @AfterThrowing(
        pointcut = "execution(* com.example.service.UserService.*(..))",
        throwing = "exception"
    )
    public void afterThrowingMethod(JoinPoint joinPoint, Exception exception) {
        System.out.println("=== 异常通知 ===");
        System.out.println("异常类型:" + exception.getClass().getName());
        System.out.println("异常信息:" + exception.getMessage());
    }

    // 5. 环绕通知:包裹整个方法执行
    @Around("execution(* com.example.service.UserService.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("=== 环绕通知 - 前置 ===");

        long startTime = System.currentTimeMillis();

        try {
            // 执行原方法
            Object result = joinPoint.proceed();

            System.out.println("=== 环绕通知 - 返回 ===");
            System.out.println("返回值:" + result);

            return result;
        } catch (Exception e) {
            System.out.println("=== 环绕通知 - 异常 ===");
            System.out.println("异常:" + e.getMessage());
            throw e;
        } finally {
            long endTime = System.currentTimeMillis();
            System.out.println("=== 环绕通知 - 后置 ===");
            System.out.println("耗时:" + (endTime - startTime) + "ms");
        }
    }
}

6.4 推荐使用@Around

// 推荐使用@Around,因为它最灵活

@Aspect
@Component
public class BestPracticeAspect {

    // ✅ 推荐:使用@Around实现所有通知的效果
    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        // 前置通知
        System.out.println("前置:方法执行前");

        long startTime = System.currentTimeMillis();
        Object result = null;
        Exception exception = null;

        try {
            // 执行原方法
            result = joinPoint.proceed();

            // 返回通知
            System.out.println("返回:" + result);

            return result;
        } catch (Exception e) {
            // 异常通知
            exception = e;
            System.out.println("异常:" + e.getMessage());
            throw e;
        } finally {
            // 后置通知
            long endTime = System.currentTimeMillis();
            System.out.println("后置:耗时" + (endTime - startTime) + "ms");
        }
    }
}

第六部分:切入点表达式

七、切入点表达式详解

7.1 切入点表达式语法

// 切入点表达式语法

// 基本格式:execution(修饰符 返回类型 包名.类名.方法名(参数))

// 1. 匹配所有方法
@Pointcut("execution(* *(..))")
public void allMethods() {}

// 2. 匹配特定包下的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePackage() {}

// 3. 匹配特定类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServiceClass() {}

// 4. 匹配特定方法
@Pointcut("execution(* com.example.service.UserService.createUser(..))")
public void createUserMethod() {}

// 5. 匹配特定参数的方法
@Pointcut("execution(* com.example.service.UserService.createUser(com.example.User))")
public void createUserWithUserParam() {}

// 6. 匹配任意参数的方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void anyParams() {}

// 7. 匹配特定返回类型的方法
@Pointcut("execution(com.example.User com.example.service.UserService.getUser(..))")
public void returnUserType() {}

// 8. 匹配void返回的方法
@Pointcut("execution(void com.example.service.UserService.*(..))")
public void voidReturn() {}

// 9. 匹配特定注解的方法
@Pointcut("@annotation(com.example.annotation.RequirePermission)")
public void annotatedMethod() {}

// 10. 匹配特定注解的类
@Pointcut("@within(com.example.annotation.Service)")
public void annotatedClass() {}

// 11. 组合切入点
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(com.example.annotation.Log)")
public void serviceAndAnnotated() {}

7.2 常用切入点表达式

// 常用的切入点表达式

@Aspect
@Component
public class CommonPointcutExpressions {

    // 1. 匹配service包下所有类的所有方法
    @Pointcut("execution(* com.example.service..*.*(..))")
    public void serviceLayer() {}

    // 2. 匹配controller包下所有类的所有方法
    @Pointcut("execution(* com.example.controller..*.*(..))")
    public void controllerLayer() {}

    // 3. 匹配所有@Service注解的类
    @Pointcut("@within(org.springframework.stereotype.Service)")
    public void serviceAnnotation() {}

    // 4. 匹配所有@RequestMapping注解的方法
    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void requestMappingAnnotation() {}

    // 5. 匹配所有public方法
    @Pointcut("execution(public * com.example..*.*(..))")
    public void publicMethods() {}

    // 6. 匹配所有protected方法
    @Pointcut("execution(protected * com.example..*.*(..))")
    public void protectedMethods() {}

    // 7. 匹配所有返回User对象的方法
    @Pointcut("execution(com.example.User com.example..*.*(..))")
    public void returnUser() {}

    // 8. 匹配所有参数为User的方法
    @Pointcut("execution(* com.example..*(com.example.User))")
    public void paramUser() {}

    // 9. 匹配所有参数为User和String的方法
    @Pointcut("execution(* com.example..*(com.example.User, String))")
    public void paramUserAndString() {}

    // 10. 匹配所有参数个数为2的方法
    @Pointcut("execution(* com.example..*(*, *))")
    public void twoParams() {}
}

第七部分:AOP的实际应用

八、日志切面

// 日志切面实现

@Aspect
@Component
public class LoggingAspect {

    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    @Around("execution(* com.example.service..*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = signature.getName();
        Object[] args = joinPoint.getArgs();

        // 记录方法调用前
        logger.info("=== 方法调用开始 ===");
        logger.info("类名:{}", className);
        logger.info("方法名:{}", methodName);
        logger.info("参数:{}", Arrays.toString(args));

        long startTime = System.currentTimeMillis();

        try {
            // 执行原方法
            Object result = joinPoint.proceed();

            // 记录返回值
            logger.info("返回值:{}", result);

            return result;
        } catch (Exception e) {
            // 记录异常
            logger.error("方法执行异常:{}", e.getMessage(), e);
            throw e;
        } finally {
            // 记录耗时
            long endTime = System.currentTimeMillis();
            logger.info("耗时:{}ms", (endTime - startTime));
            logger.info("=== 方法调用结束 ===");
        }
    }
}

九、性能监控切面

// 性能监控切面实现

@Aspect
@Component
public class PerformanceMonitoringAspect {

    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitoringAspect.class);

    // 定义切入点
    @Pointcut("execution(* com.example.service..*.*(..))")
    public void serviceLayer() {}

    @Around("serviceLayer()")
    public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();

        long startTime = System.currentTimeMillis();

        try {
            return joinPoint.proceed();
        } finally {
            long endTime = System.currentTimeMillis();
            long duration = endTime - startTime;

            // 记录耗时
            logger.info("方法:{},耗时:{}ms", methodName, duration);

            // 如果超过阈值,发送告警
            if (duration > 1000) {
                logger.warn("方法{}执行过慢,耗时{}ms", methodName, duration);
                // 发送告警通知
                sendAlert(methodName, duration);
            }
        }
    }

    private void sendAlert(String methodName, long duration) {
        // 发送告警逻辑
        System.out.println("发送告警:方法" + methodName + "执行过慢");
    }
}

十、权限检查切面

// 权限检查切面实现

// 1. 定义权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
    String value();
}

// 2. 定义权限检查切面
@Aspect
@Component
public class PermissionCheckAspect {

    @Autowired
    private UserService userService;

    @Before("@annotation(requirePermission)")
    public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) {
        // 获取当前用户
        User currentUser = getCurrentUser();

        if (currentUser == null) {
            throw new SecurityException("用户未登录");
        }

        // 检查权限
        String requiredPermission = requirePermission.value();
        if (!userService.hasPermission(currentUser.getId(), requiredPermission)) {
            throw new SecurityException("用户无权限:" + requiredPermission);
        }
    }

    private User getCurrentUser() {
        // 从SecurityContext获取当前用户
        return null;  // 实现细节
    }
}

// 3. 使用权限注解
@Service
public class UserService {

    @RequirePermission("DELETE_USER")
    public void deleteUser(int id) {
        System.out.println("删除用户:" + id);
    }

    @RequirePermission("UPDATE_USER")
    public void updateUser(User user) {
        System.out.println("更新用户:" + user.getName());
    }
}

十一、事务管理切面

// 事务管理切面实现

@Aspect
@Component
public class TransactionAspect {

    @Autowired
    private TransactionManager transactionManager;

    // 匹配所有@Transactional注解的方法
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        // 开启事务
        TransactionStatus status = transactionManager.getTransaction(
            new DefaultTransactionDefinition()
        );

        try {
            // 执行业务逻辑
            Object result = joinPoint.proceed();

            // 提交事务
            transactionManager.commit(status);

            return result;
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(status);
            throw e;
        }
    }
}

// 使用@Transactional注解
@Service
public class OrderService {

    @Transactional
    public void createOrder(Order order) {
        // 业务逻辑
        // 如果发生异常,事务自动回滚
    }
}

十二、缓存切面

// 缓存切面实现

// 1. 定义缓存注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
    String value();
    long timeout() default 3600;  // 秒
}

// 2. 定义缓存切面
@Aspect
@Component
public class CacheAspect {

    private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();

    @Around("@annotation(cacheable)")
    public Object cacheMethod(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
        // 生成缓存key
        String cacheKey = generateCacheKey(joinPoint, cacheable);

        // 检查缓存
        CacheEntry entry = cache.get(cacheKey);
        if (entry != null && !entry.isExpired()) {
            System.out.println("从缓存返回:" + cacheKey);
            return entry.getValue();
        }

        // 执行原方法
        Object result = joinPoint.proceed();

        // 存入缓存
        cache.put(cacheKey, new CacheEntry(result, cacheable.timeout()));

        return result;
    }

    private String generateCacheKey(ProceedingJoinPoint joinPoint, Cacheable cacheable) {
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        return cacheable.value() + ":" + methodName + ":" + args;
    }

    static class CacheEntry {
        private final Object value;
        private final long expireTime;

        public CacheEntry(Object value, long timeout) {
            this.value = value;
            this.expireTime = System.currentTimeMillis() + timeout * 1000;
        }

        public boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }

        public Object getValue() {
            return value;
        }
    }
}

// 3. 使用缓存注解
@Service
public class UserService {

    @Cacheable("user")
    public User getUserById(int id) {
        System.out.println("从数据库查询用户:" + id);
        return new User(id, "Tom");
    }
}

第八部分:切面的执行顺序

十三、多切面的执行顺序

13.1 使用@Order控制顺序

// 多切面的执行顺序控制

// 1. 权限检查切面(最先执行)
@Aspect
@Component
@Order(1)
public class PermissionAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void checkPermission(JoinPoint joinPoint) {
        System.out.println("1. 权限检查");
    }
}

// 2. 日志切面
@Aspect
@Component
@Order(2)
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logging(JoinPoint joinPoint) {
        System.out.println("2. 记录日志");
    }
}

// 3. 性能监控切面(最后执行)
@Aspect
@Component
@Order(3)
public class PerformanceAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void monitoring(JoinPoint joinPoint) {
        System.out.println("3. 性能监控");
    }
}

// 执行顺序:
// 前置通知:1 → 2 → 3
// 执行原方法
// 后置通知:3 → 2 → 1

13.2 切面执行顺序的详细流程

┌──────────────────────────────────────────┐
│         多切面的执行顺序                  │
├──────────────────────────────────────────┤
│                                          │
│  @Order(1) - PermissionAspect            │
│  ├─ @Before:权限检查                    │
│  │                                       │
│  @Order(2) - LoggingAspect               │
│  ├─ @Before:记录日志                    │
│  │                                       │
│  @Order(3) - PerformanceAspect           │
│  ├─ @Before:性能监控                    │
│  │                                       │
│  ├─ 执行原方法                           │
│  │                                       │
│  @Order(3) - PerformanceAspect           │
│  ├─ @After:性能监控后置                 │
│  │                                       │
│  @Order(2) - LoggingAspect               │
│  ├─ @After:日志后置                     │
│  │                                       │
│  @Order(1) - PermissionAspect            │
│  ├─ @After:权限检查后置                 │
│                                          │
└──────────────────────────────────────────┘

第九部分:AOP的最佳实践

十四、推荐做法

14.1 优先使用 @Around,而不是同时写一堆通知

能用一个 @Around 表达的,就不要再拆成 @Before / @AfterReturning / @AfterThrowing / @After 四连。

@Aspect
@Component
public class GoodAspect {

    // ✅ 推荐:使用 @Around 实现所有通知的效果
    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("前置逻辑");

        try {
            Object result = joinPoint.proceed(); // 执行业务方法
            System.out.println("返回逻辑");
            return result;
        } catch (Exception e) {
            System.out.println("异常逻辑");
            throw e;
        } finally {
            System.out.println("后置逻辑");
        }
    }
}
  • 实际上 @Around 可以覆盖所有类型通知;
  • 逻辑集中在一个地方,方便维护、调试;
  • 避免多个通知顺序、重复执行的问题。

14.2 使用 @Pointcut 提取公共切入点

不要到处复制粘贴 execution(...),统一抽成 @Pointcut,维护成本低很多。

@Aspect
@Component
public class BestPracticeAspect {

    // Service 层切点
    @Pointcut("execution(* com.example.service..*.*(..))")
    public void serviceLayer() {}

    // Controller 层切点
    @Pointcut("execution(* com.example.controller..*.*(..))")
    public void controllerLayer() {}

    // 组合切点:Controller + Service
    @Pointcut("serviceLayer() || controllerLayer()")
    public void allLayers() {}

    @Around("serviceLayer()")
    public Object monitorService(ProceedingJoinPoint joinPoint) throws Throwable {
        // 可以加耗时统计、统一异常转换等
        return joinPoint.proceed();
    }

    @Around("controllerLayer()")
    public Object monitorController(ProceedingJoinPoint joinPoint) throws Throwable {
        // 可以加统一日志、请求参数记录等
        return joinPoint.proceed();
    }
}

关键点:

  • 把「横切范围」抽象成名字(serviceLayercontrollerLayer),比一串 execution(...) 可读多了;
  • 支持组合切点:&&||!
  • 后续包结构、类名改变,只用改一个地方的表达式。

14.3 使用自定义注解定义“语义切点”

比起按包名/类名切,更推荐按“业务语义”切,比如:@OpLog@Audit@RateLimit@DistributedLock 等。

1)定义自定义注解

// 例如:操作日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OpLog {
    String value() default "";   // 描述:比如「创建订单」「删除用户」
}

2)在业务方法上使用

@Service
public class OrderService {

    @OpLog("创建订单")
    public void createOrder(String userId, String productId) {
        // 业务逻辑...
    }

    @OpLog("取消订单")
    public void cancelOrder(String orderId) {
        // 业务逻辑...
    }
}

3)编写切面:通过 @annotation 匹配

@Aspect
@Component
public class OpLogAspect {

    @Around("@annotation(opLog)")
    public Object logOperation(ProceedingJoinPoint joinPoint, OpLog opLog) throws Throwable {
        String desc = opLog.value();
        String method = joinPoint.getSignature().toShortString();

        long start = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            long cost = System.currentTimeMillis() - start;

            System.out.printf("【操作日志】%s - 方法:%s - 耗时:%d ms - 状态:成功%n",
                    desc, method, cost);
            return result;
        } catch (Throwable ex) {
            long cost = System.currentTimeMillis() - start;

            System.out.printf("【操作日志】%s - 方法:%s - 耗时:%d ms - 状态:异常:%s%n",
                    desc, method, cost, ex.getMessage());
            throw ex;
        }
    }
}

这样做的好处:

  • 业务代码上只需要加一个注解,语义非常清晰;
  • AOP 切面只关心「带这个注解的方法」,和包结构解耦
  • 方便按“功能”划分切面:日志切面、鉴权切面、限流切面、分布式锁切面……

⬅️ 依赖注入(DI) 🏠 00-Java ➡️ 04-Bean生命周期与三级缓存