Skip to content

AOP 详解

  AOP(Aspect Oriented Programming,面向切面编程)是 Spring 两大核心之一。 它把横切关注点(日志、事务、权限)从业务代码中抽离出来,形成独立的"切面"。

一、为什么需要 AOP

没有 AOP 的时代:

  public void createOrder() {
      log.info("开始创建订单");           // ① 日志
      checkPermission();                 // ② 权限
      // ===== 业务逻辑 =====
      orderDao.insert(order);
      // ===== 业务逻辑 =====
      transaction.commit();              // ③ 事务
      log.info("订单创建成功");           // ④ 日志
  }

  问题:日志、权限、事务散落在每个方法中,重复代码多,维护成本高


有了 AOP 之后:

  public void createOrder() {
      // ===== 只有业务逻辑 =====
      orderDao.insert(order);
  }

  日志、权限、事务由切面自动织入,业务代码零侵入

二、AOP 核心概念

AOP 的五个核心概念:

  ① JoinPoint(连接点):
     程序执行过程中的某个点,如方法调用、异常抛出
     Spring AOP 只支持方法级别的连接点

  ② Pointcut(切点):
     匹配连接点的表达式,决定在哪些方法上应用切面
     例:execution(* com.example.service.*.*(..))
     → 匹配 service 包下所有方法

  ③ Advice(通知):
     在切点处执行的动作,即"增强的逻辑"
     分为:Before、After、AfterReturning、AfterThrowing、Around

  ④ Aspect(切面):
     切点 + 通知的组合,即"在哪里、做什么"
     @Aspect 注解的类

  ⑤ Weaving(织入):
     将切面应用到目标对象并创建代理对象的过程
     编译期织入、类加载期织入、运行期织入
     Spring AOP 使用运行期织入(动态代理)

三、五种通知类型

java
@Aspect
@Component
public class LogAspect {

    // ① @Before:方法执行前
    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint joinPoint) {
        String method = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println(">>> 开始执行 " + method + ",参数:" + Arrays.toString(args));
    }

    // ② @AfterReturning:方法正常返回后(可以获取返回值)
    @AfterReturning(value = "execution(* com.example.service.*.*(..))",
                    returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println(">>> 执行成功,返回值:" + result);
    }

    // ③ @AfterThrowing:方法抛出异常后
    @AfterThrowing(value = "execution(* com.example.service.*.*(..))",
                   throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        System.out.println(">>> 执行异常:" + ex.getMessage());
    }

    // ④ @After:方法执行后(无论成功还是异常,相当于 finally)
    @After("execution(* com.example.service.*.*(..))")
    public void after(JoinPoint joinPoint) {
        System.out.println(">>> 执行结束");
    }

    // ⑤ @Around:环绕通知(最强大,可以控制方法是否执行)
    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String method = joinPoint.getSignature().getName();
        long start = System.currentTimeMillis();

        System.out.println(">>> " + method + " 开始");
        Object result = joinPoint.proceed();  // 执行目标方法(必须手动调用)
        long elapsed = System.currentTimeMillis() - start;
        System.out.println(">>> " + method + " 结束,耗时:" + elapsed + "ms");

        return result;
    }
}

四、切点表达式

execution 表达式语法:

  execution(修饰符? 返回值类型 包名.类名?方法名(参数类型) throws 异常?)

  例:
  execution(* com.example.service.*.*(..))
      ↑               ↑       ↑ ↑  ↑
      |               |       | |  └── 任意参数
      |               |       | └── 任意方法名
      |               |       └── 任意类名
      |               └── service 包
      └── 任意返回值

常用切点表达式:

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

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

  // 匹配以 save 开头的方法
  execution(* com.example..*.save*(..))

  // 匹配指定参数类型的方法(第一个参数为 Long)
  execution(* com.example..*.*(Long, ..))

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

  // 匹配标有 @Transactional 的方法
  @annotation(org.springframework.transaction.annotation.Transactional)

五、AOP 底层原理

5.1 JDK 动态代理 vs CGLIB

JDK 动态代理:
  条件:目标类必须实现接口
  原理:Proxy.newProxyInstance() 生成代理对象
  代理对象和目标对象是兄弟关系(实现同一接口)

  ┌──────────────┐      ┌──────────────────┐
  │  OrderService │      │  $Proxy (代理)    │
  │  (目标对象)   │      │  (实现相同接口)    │
  └──────────────┘      └──────────────────┘
           ↑                      ↑
           └──── 都实现 OrderDao ──┘


CGLIB 代理:
  条件:不需要接口
  原理:继承目标类,生成子类代理
  代理对象是目标对象的子类

  ┌──────────────────┐
  │  OrderService     │
  │  (目标对象)       │
  └────────┬─────────┘
           │ 继承
  ┌────────▼─────────┐
  │  OrderService$$   │
  │  EnhancerByCGLIB  │  ← 代理对象是子类
  └──────────────────┘
java
// Spring Boot 2.x 默认使用 CGLIB(proxy-target-class=true)
// Spring Boot 3.x 默认使用 CGLIB(不再需要接口)

// 强制使用 JDK 动态代理
@EnableAspectJAutoProxy(proxyTargetClass = false)

5.2 代理对象创建时机

AOP 代理对象在 Bean 生命周期中创建:

  ① 实例化 → ② 属性填充 → ...
  → ⑨ BeanPostProcessor.postProcessAfterInitialization()
      → AbstractAutoProxyCreator.wrapIfNecessary()
          → 判断是否需要创建代理
          → 需要 → 创建代理对象
          → 不需要 → 返回原始对象
  → ⑩ Bean 就绪(此时容器中已经是代理对象了)

六、AOP 实战场景

java
// ① 方法耗时统计
@Aspect
@Component
public class PerformanceAspect {
    @Around("@annotation(com.example.annotation.Monitor)")
    public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long elapsed = System.currentTimeMillis() - start;

        if (elapsed > 1000) {
            log.warn("慢方法:{} 耗时 {}ms", joinPoint.getSignature(), elapsed);
        }
        return result;
    }
}

// ② 统一异常处理
@Aspect
@Component
public class ExceptionAspect {
    @Around("execution(* com.example.controller..*.*(..))")
    public Object handleException(ProceedingJoinPoint joinPoint) {
        try {
            return joinPoint.proceed();
        } catch (BusinessException e) {
            return Result.error(e.getCode(), e.getMessage());
        } catch (Exception e) {
            log.error("系统异常", e);
            return Result.error("500", "系统异常");
        }
    }
}

// ③ 分布式锁(自定义注解 + AOP)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    String key();
    long timeout() default 10;
}

@Aspect
@Component
public class RedisLockAspect {
    @Autowired
    private RedissonClient redisson;

    @Around("@annotation(redisLock)")
    public Object lock(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        RLock lock = redisson.getLock(redisLock.key());
        if (lock.tryLock(redisLock.timeout(), TimeUnit.SECONDS)) {
            try {
                return joinPoint.proceed();
            } finally {
                lock.unlock();
            }
        }
        throw new RuntimeException("获取锁失败");
    }
}

七、AOP 失效场景

java
// ① 同类内部调用(this 调用)
@Service
public class OrderService {
    public void outer() {
        this.inner();  // ❌ AOP 不生效!this 是目标对象,不是代理对象
    }

    @Transactional
    public void inner() { }
}

// 解决:注入自己
@Service
public class OrderService {
    @Autowired
    private OrderService self;  // 注入代理对象

    public void outer() {
        self.inner();  // ✅ 通过代理调用,AOP 生效
    }
}

// ② private 方法
@Transactional
private void inner() { }  // ❌ CGLIB 不能代理 private 方法

// ③ final 方法
@Transactional
public final void inner() { }  // ❌ CGLIB 不能代理 final 方法

// ④ static 方法
@Transactional
public static void inner() { }  // ❌ 代理对象不拦截 static 方法

八、面试要点

Q1:JDK 动态代理和 CGLIB 的区别?

  JDK 动态代理要求目标类实现接口,生成的是兄弟关系(实现同一接口);CGLIB 通过继承目标类生成子类代理,不需要接口。JDK 原生支持,CGLIB 是第三方库。Spring Boot 默认使用 CGLIB。

Q2:@Around 和 @Before/@After 的区别?

  @Around 可以完全控制方法执行(决定是否调用、修改参数、修改返回值),必须手动调用 joinPoint.proceed()。@Before/@After 只能做前置/后置处理,不能控制方法是否执行。

Q3:AOP 在什么情况下会失效?

  同类 this 内部调用(绕过了代理)、private 方法、final 方法、static 方法、调用方和被调用方不在同一个 Spring 容器中。