AOP(面向切面编程)是 Spring 三大核心(IoC、AOP、事务)之一,也是面试高频考点。它解决了 OOP(面向对象编程)中通用逻辑与业务逻辑耦合的问题,让日志、权限、事务、性能监控这些通用功能与业务代码完全解耦,无需修改原有业务代码即可添加增强功能。

本文用最通俗的语言、最直观的示例,带你从零掌握 Spring AOP 所有核心知识点:核心概念、五大通知、切点表达式、切面优先级、底层原理。


一、核心概念

先给你一个最容易理解的比喻:

假设你开了一家餐厅,所有厨师都在专心炒菜(业务逻辑)。现在需要给每道菜加一道 "消毒" 工序(通用逻辑)。

  • 不用 AOP:每个厨师炒菜前都要自己消毒,代码重复、耦合严重
  • 用 AOP:雇一个专门的消毒员,在所有菜下锅前自动消毒,厨师只需要专心炒菜

AOP 的核心思想:横向切割业务流程,在不修改原有业务代码的前提下,在指定位置插入通用逻辑。

5 个必懂核心术语

术语通俗解释代码对应
连接点(JoinPoint)程序执行过程中可以被拦截的点,通常是方法所有可以被增强的方法
切点(PointCut)实际需要被拦截的连接点(筛选后的连接点)你写的表达式匹配到的方法
通知(Advice)拦截到方法后要执行的通用逻辑("消毒" 动作)日志、权限、事务代码
切面(Aspect)切点 + 通知的组合("消毒员")@Aspect 标记的类
目标对象(Target)被增强的对象("厨师")业务类对象

简单记:切面 = 在哪里切(切点) + 切了干什么(通知)


二、五大通知类型(核心)

通知定义了在切点方法的什么时机执行增强逻辑,Spring AOP 提供 5 种通知注解:

1. 五大通知详解

注解执行时机常用场景
@Before目标方法执行前执行参数校验、权限检查
@After目标方法执行后执行(无论是否抛出异常)资源释放、日志收尾
@Around目标方法执行前后都执行性能监控、事务管理(最强大)
@AfterReturning目标方法正常返回后执行结果处理、日志记录
@AfterThrowing目标方法抛出异常后执行异常日志、告警通知

2. 完整代码示例(统一演示)

第一步:引入依赖(SpringBoot 项目)
<!-- Spring AOP 起步依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步:编写业务类(被增强的目标)
import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void addUser(String name) {
        System.out.println("执行业务逻辑:添加用户 " + name);
    }

    public String getUserById(Integer id) {
        System.out.println("执行业务逻辑:查询用户 " + id);
        return "用户" + id;
    }
}
第三步:编写日志切面(核心)
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

// 标记为切面类 + 交给Spring管理
@Aspect
@Component
public class LogAspect {

    // 定义切点:匹配UserService下的所有方法
    @Pointcut("execution(* com.yanglei.service.UserService.*(..))")
    public void logPointCut() {}

    // 前置通知:方法执行前
    @Before("logPointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("【前置通知】方法名:" + joinPoint.getSignature().getName());
    }

    // 后置通知:方法执行后(无论是否异常)
    @After("logPointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("【后置通知】方法执行完成");
    }

    // 环绕通知:方法执行前后(最强大)
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("【环绕通知-前】开始计时");
        long start = System.currentTimeMillis();

        // 执行目标方法(必须手动调用,否则目标方法不会执行!)
        Object result = pjp.proceed();

        long end = System.currentTimeMillis();
        System.out.println("【环绕通知-后】方法耗时:" + (end - start) + "ms");
        return result;
    }

    // 返回通知:方法正常返回后
    @AfterReturning(value = "logPointCut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("【返回通知】方法返回值:" + result);
    }

    // 异常通知:方法抛出异常后
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        System.out.println("【异常通知】方法抛出异常:" + e.getMessage());
    }
}

3. 执行顺序

运行测试调用 addUser() 方法,输出顺序:        

【环绕通知-前】开始计时
【前置通知】方法名:addUser
执行业务逻辑:添加用户 张三
【返回通知】方法返回值:null
【后置通知】方法执行完成
【环绕通知-后】方法耗时:1ms

关键注意点

  • @Around 必须手动调用 pjp.proceed() 执行目标方法,否则目标方法不会运行
  • @AfterThrowing 只有方法抛出异常时才会执行
  • @After 无论方法是否抛出异常都会执行

三、@PointCut 切点复用

@PointCut 注解专门用于定义可复用的切点表达式,避免多个通知重复写相同的表达式。

1. 基本用法

// 定义切点
@Pointcut("execution(* com.yanglei.service.*.*(..))")
public void servicePointCut() {}

// 引用切点
@Before("servicePointCut()")
public void before() {}

2. 跨类引用切点

如果多个切面需要使用同一个切点,可以定义一个公共切点类:

// 公共切点类
public class CommonPointCut {
    @Pointcut("execution(* com.yanglei.service.*.*(..))")
    public static void allServicePointCut() {}
}

// 其他切面引用
@Before("com.yanglei.aspect.CommonPointCut.allServicePointCut()")
public void before() {}

四、常用切点表达式(两种核心)

切点表达式用于精准匹配需要增强的方法,最常用的有两种:execution 表达式和 @annotation 表达式。

1. execution 表达式(最常用)

用于根据方法签名匹配方法,语法格式:

execution(修饰符 返回值类型 包名.类名.方法名(参数列表) 异常)
常用写法示例
// 匹配UserService下的所有方法
execution(* com.yanglei.service.UserService.*(..))

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

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

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

// 匹配所有返回值为String的方法
execution(String *(..))
符号说明
  • *:匹配任意字符
  • ..:匹配任意参数列表
  • .:包分隔符

2. @annotation 表达式(最灵活)

用于匹配被指定注解标记的方法,适合自定义业务场景(比如给需要日志的方法加自定义注解)。

步骤 1:自定义注解
import java.lang.annotation.*;

// 自定义日志注解
@Target(ElementType.METHOD) // 只能标记在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface Log {
    String value() default "";
}
步骤 2:在业务方法上标记注解
@Service
public class UserService {
    @Log("添加用户") // 标记需要日志的方法
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}
步骤 3:编写切面匹配注解
@Aspect
@Component
public class LogAnnotationAspect {
    // 匹配所有被@Log注解标记的方法
    @Before("@annotation(log)")
    public void before(JoinPoint joinPoint, Log log) {
        System.out.println("【自定义注解日志】操作:" + log.value());
    }
}

优势:粒度更细,哪个方法需要增强就给哪个方法加注解,灵活可控。


五、切面优先级 @Order

多个切面作用于同一个方法时,需要用 @Order 注解指定切面的执行顺序。

1. 优先级规则

  • @Order(n):n 是整数,数字越小,优先级越高
  • 优先级高的切面:前置通知先执行,后置通知后执行(先进后出)

2. 示例

// 切面1:优先级1(最高)
@Aspect
@Component
@Order(1)
public class AuthAspect {
    @Before("execution(* com.yanglei.service.*.*(..))")
    public void before() {
        System.out.println("【权限校验】");
    }
}

// 切面2:优先级2
@Aspect
@Component
@Order(2)
public class LogAspect {
    @Before("execution(* com.yanglei.service.*.*(..))")
    public void before() {
        System.out.println("【日志记录】");
    }
}

执行顺序:

【权限校验】(优先级高先执行)
【日志记录】
执行业务逻辑

六、Spring AOP 底层原理(面试)

Spring AOP 的底层是动态代理,Spring 会在运行时动态生成代理对象,通过代理对象调用目标方法,在调用过程中插入增强逻辑。

1. 两种动态代理方式

Spring AOP 支持两种动态代理实现,自动根据目标对象是否实现接口选择:

表格

代理方式实现原理适用场景限制
JDK 动态代理基于接口实现,生成目标接口的实现类代理目标对象实现了接口只能代理实现了接口的类
CGLIB 动态代理基于继承实现,生成目标类的子类代理目标对象没有实现接口不能代理 final 类和 final 方法

2. SpringBoot 默认配置

  • SpringBoot 2.x 及以上版本:默认使用 CGLIB 动态代理,即使目标对象实现了接口
  • 如需强制使用 JDK 动态代理,可在 application.yml 中配置:
    spring:
      aop:
        proxy-target-class: false

3. AOP 执行流程

  1. Spring 容器启动时,扫描所有 @Aspect 注解的切面类
  2. 为匹配切点的目标对象生成代理对象
  3. 当调用目标方法时,实际调用的是代理对象的方法
  4. 代理对象按照通知顺序,依次执行前置、目标方法、后置等增强逻辑

七、总结

1. 核心知识点速记

  • AOP 核心:横向切割,解耦通用逻辑与业务逻辑
  • 五大通知:@Before、@After、@Around、@AfterReturning、@AfterThrowing
  • 切点表达式:execution(按方法匹配)、@annotation(按注解匹配)
  • 切面优先级:@Order (n),数字越小优先级越高
  • 底层原理:JDK 动态代理(接口)、CGLIB 动态代理(继承)

2. 面试高频问题

  • AOP 和 OOP 的区别?OOP 是纵向继承,解决业务代码复用;AOP 是横向切割,解决通用逻辑复用。
  • Spring AOP 和 AspectJ 的区别?Spring AOP 是运行时动态代理,只能代理方法;AspectJ 是编译时织入,功能更强大,支持字段、构造器等增强。
  • @Around 为什么必须调用 proceed ()?@Around 会完全拦截目标方法的执行,必须手动调用 proceed () 才能触发目标方法运行。

。        

Logo

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

更多推荐