AOP(面向切面编程)概述

AOP(Aspect-Oriented Programming) 是一种编程范式,通过将横切关注点从业务逻辑中分离出来,实现代码的模块化和复用。

核心思想

  • 将分散在多个模块中的相同逻辑(如日志、事务、权限校验)提取到一个独立的模块中
  • 通过声明式的方式将这些逻辑"织入"到业务代码中
  • 减少重复代码,提高可维护性

Spring AOP 核心概念

1. 切面

  • 封装横切逻辑的模块
  • 是一个类,包含通知和切点
  • 使用 @Aspect 注解标识
@Aspect
@Component
public class LoggingAspect {
    // 切面类,包含通知和切点
}

2. 连接点

  • 程序执行的某个特定位置
  • 在 Spring AOP 中,连接点通常是方法的执行
  • 理论上可以是字段访问、异常处理等,但 Spring 只支持方法级别的连接点
// 下面所有方法的执行都是潜在的连接点
public class UserService {
    public void createUser() { }      // 连接点
    public void updateUser() { }      // 连接点
    public void deleteUser() { }      // 连接点
}

3. 通知

  • 在连接点执行的代码
  • 定义了"何时"以及"如何"执行切面逻辑
通知类型 说明 注解
前置通知 方法执行前 @Before
后置通知 方法执行后(无论成功或失败) @After
返回通知 方法成功返回后 @AfterReturning
异常通知 方法抛出异常后 @AfterThrowing
环绕通知 包裹方法执行,可控制是否执行 @Around
@Aspect
@Component
public class LoggingAspect {
    
    // 前置通知
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        System.out.println("方法执行前: " + joinPoint.getSignature().getName());
    }
    
    // 后置通知
    @After("execution(* com.example.service.*.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("方法执行后: " + joinPoint.getSignature().getName());
    }
    
    // 返回通知
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("方法返回: " + result);
    }
    
    // 异常通知
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        System.out.println("方法异常: " + ex.getMessage());
    }
    
    // 环绕通知(最强大)
    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知 - 前");
        Object result = joinPoint.proceed(); // 执行目标方法
        System.out.println("环绕通知 - 后");
        return result;
    }
}

4. 切点

  • 匹配连接点的表达式
  • 定义了"在哪里"执行通知
  • 使用 AspectJ 切点表达式语言
@Aspect
@Component
public class LoggingAspect {
    
    // 切点定义
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    // 复用切点
    @Before("serviceLayer()")
    public void beforeServiceMethod() {
        System.out.println("Service 层方法执行前");
    }
}

切点表达式示例

// 匹配所有 public 方法
execution(public * *(..))

// 匹配所有以 set 开头的方法
execution(* set*(..))

// 匹配 UserService 类的所有方法
execution(* com.example.UserService.*(..))

// 匹配 service 包下所有类的所有方法
execution(* com.example.service.*.*(..))

// 匹配 service 包及其子包下所有类的所有方法
execution(* com.example.service..*.*(..))

// 匹配第一个参数是 String 的方法
execution(* *(String, ..))

// 匹配所有带 @Transactional 注解的方法
@annotation(org.springframework.transaction.annotation.Transactional)

完整示例

@Aspect
@Component
public class TransactionAspect {
    
    // 切点:匹配 service 包下所有方法
    @Pointcut("execution(* com.example.service..*.*(..))")
    public void serviceMethods() {}
    
    // 环绕通知:事务管理
    @Around("serviceMethods()")
    public Object manageTransaction(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;
        }
    }
}

概念关系图

┌─────────────────────────────────────────────────────────┐
│                      切面                                │
│  ┌─────────────────────────────────────────────────┐   │
│  │  通知(Advice)                                   │   │
│  │  - @Before                                       │   │
│  │  - @After                                        │   │
│  │  - @Around                                       │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │  切点(Pointcut)                                 │   │
│  │  execution(* com.example.service.*.*(..))       │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
                          │
                          │ 匹配
                          ▼
┌─────────────────────────────────────────────────────────┐
│                  连接点(JoinPoint)                     │
│  UserService.createUser()  ────  目标方法执行           │
│  UserService.updateUser()  ────  目标方法执行           │
└─────────────────────────────────────────────────────────┘

Spring AOP vs AspectJ

特性 Spring AOP AspectJ
实现方式 代理模式(JDK 动态代理 / CGLIB) 编译期/类加载期织入
支持的连接点 仅方法 方法、字段、构造器等
性能 稍低(代理调用) 更高(直接织入)
复杂度 简单,Spring 集成 复杂,需要额外配置
适用场景 大多数业务场景 需要细粒度控制时

总结

概念 作用 问题
切面 封装横切逻辑的模块 是什么?
连接点 程序执行的特定位置(方法) 在哪里?
通知 在连接点执行的代码 何时做?如何做?
切点 匹配连接点的表达式 在哪些连接点?
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐