Spring AOP核心指南:入门

发布时间:2026/10/31 1:37:30
Spring AOP核心指南:入门

一、AOP 概述

1. 什么是 AOP

全称:Aspect Oriented Programming(面向切面编程 / 面向方面编程)本质:面向特定方法编程,在不修改原方法代码的前提下,对方法进行增强(如统计耗时、日志记录、事务控制)。

核心实现:动态代理(Spring AOP 底层通过动态代理机制实现对 Bean 方法的增强)。

二、Spring AOP 快速入门

1. 依赖导入(Maven)

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>

2. 编写切面类

@Component @Aspect public class TimeAspect { // 环绕通知:拦截service包下所有类的所有方法 @Around("execution(* com.itheima.service.*.*(..))") public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 1. 获取方法执行开始时间 long begin = System.currentTimeMillis(); // 2. 调用原始方法 Object result = proceedingJoinPoint.proceed(); // 3. 获取方法执行结束时间,计算耗时 long end = System.currentTimeMillis(); log.info("{} 执行耗时: {}ms", proceedingJoinPoint.getSignature(), end - begin); return result; } }

三、AOP 核心概念

概念说明
连接点(JoinPoint)可以被 AOP 控制的方法(包含方法执行的相关信息,如方法名、参数等)
通知(Advice)指那些重复的、需要增强的公共逻辑(最终体现为一个方法,如统计耗时、日志)
切入点(PointCut)匹配连接点的条件,通知仅会在匹配的方法执行时被应用
切面(Aspect)描述通知与切入点的对应关系(通知 + 切入点),是 AOP 的核心类
目标对象(Target)通知所应用的原始对象(即被增强的 Bean 对象)

四、通知类型(5 种)

注解通知类型执行时机说明
@Around环绕通知目标方法执行前后都被执行功能最强大,可控制方法执行、修改返回值
@Before前置通知目标方法执行前被执行无法控制方法执行,不能修改返回值
@After后置通知目标方法执行后被执行无论方法是否异常都会执行
@AfterReturning返回后通知目标方法正常执行完成后被执行方法异常时不会执行
@AfterThrowing异常后通知目标方法发生异常后被执行方法正常执行时不会执行

关键注意事项

  1. @Around环绕通知必须手动调用proceedingJoinPoint.proceed()来让原始方法执行,其他通知无需手动调用。
  2. @Around方法的返回值类型必须为Object,用于接收原始方法的返回值。

五、@Pointcut 注解

1. 作用

将公共的切入点表达式抽取出来,通过引用方式复用,避免重复编写。

2. 用法示例

@Component @Aspect public class TimeAspect { // 定义公共切入点:匹配DeptServiceImpl类的所有方法 @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))") public void pt(){} // 引用公共切入点 @Around("pt()") public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // ... 增强逻辑 } }

3. 访问修饰符

private:仅能在当前切面类中引用该表达式

public:其他外部切面类也可以引用该表达式


六、通知执行顺序

当多个切面的切入点都匹配目标方法时,通知的执行顺序规则如下:

  1. 不同切面类中:默认按照切面类的类名字母排序
    • 目标方法前的通知:字母排名靠前的先执行
    • 目标方法后的通知:字母排名靠前的后执行
  2. 自定义顺序:使用@Order(数字)注解控制切面类的执行顺序
    • 数字越小,优先级越高
    • 目标方法前的通知:数字小的先执行
    • 目标方法后的通知:数字小的后执行

超直观的例子:

场景设定

我们有两个切面类,它们都匹配同一个目标方法,都包含@Before和@After通知:

切面类名

注解

通知方法

MyAspectA

无 @Order

@Before、@After

MyAspectB

无 @Order

@Before、@After

目标方法:service.doSomething()

一、默认顺序(按类名字母排序)

因为A在字母表中比B靠前,所以顺序如下:

MyAspectA.@Before (先执行,字母靠前) ↓ MyAspectB.@Before (后执行,字母靠后) ↓ 目标方法 service.doSomething() (执行) ↓ MyAspectB.@After (先执行,字母靠后) ↓ MyAspectA.@After (后执行,字母靠前)

控制台输出

MyAspectA Before... MyAspectB Before... 目标方法执行... MyAspectB After... MyAspectA After...

二、自定义顺序(用 @Order 控制)

现在我们给切面加上@Order注解:

MyAspectA加上@Order(2)

MyAspectB加上@Order(1)

@Order数字越小,优先级越高。

MyAspectB.@Before (数字1更小,先执行) ↓ MyAspectA.@Before (数字2更大,后执行) ↓ 目标方法 service.doSomething() (执行) ↓ MyAspectA.@After (数字2更大,先执行) ↓ MyAspectB.@After (数字1更小,后执行)

控制台输出

MyAspectB Before... MyAspectA Before... 目标方法执行... MyAspectA After... MyAspectB After...

三、关键结论(代码演示)

// 切面A:Order值为2 @Component @Aspect @Order(2) public class MyAspectA { @Before("execution(* com.example.service.*.*(..))") public void before() { System.out.println("A Before..."); } @After("execution(* com.example.service.*.*(..))") public void after() { System.out.println("A After..."); } } // 切面B:Order值为1(优先级更高) @Component @Aspect @Order(1) public class MyAspectB { @Before("execution(* com.example.service.*.*(..))") public void before() { System.out.println("B Before..."); } @After("execution(* com.example.service.*.*(..))") public void after() { System.out.println("B After..."); } }

运行结果:

B Before... // Order小,前置先执行 A Before... 目标方法执行... A After... // Order大,后置先执行 B After... // Order小,后置后执行

总结:

前置通知(@Before):谁优先级高(字母靠前 / Order 小),谁先执行。

后置通知(@After/@AfterReturning):谁优先级高(字母靠前 / Order 小),谁后执行。


七、切入点表达式(Pointcut Expression)

1. 作用

决定项目中哪些方法需要被通知增强。

2. 常见形式

① execution 表达式(按方法签名匹配)

语法格式:

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

其中?表示可省略的部分(访问修饰符、包名、类名、异常声明均可省略)。

通配符说明:

*:单个独立的任意符号,可匹配任意返回值、包名、类名、方法名、任意类型的单个参数

..:多个连续的任意符号,可匹配任意层级的包、任意类型、任意个数的参数

示例:

// 匹配service包下所有类的所有方法 @Before("execution(* com.itheima.service.*.*(..))") // 匹配DeptServiceImpl类的delete方法,参数为Integer类型 @Before("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))") // 匹配service包下所有以update开头的方法 @Before("execution(* com.itheima.service.*.update*(..))")
② @annotation 表达式(按注解匹配)

作用:匹配标识有特定注解的方法(如你项目中的@AutoFill)。

语法格式:

@annotation(注解全类名)
  • 示例:
    // 匹配所有加了@Log注解的方法 @Before("@annotation(com.itheima.anno.Log)") public void before(){ log.info("前置通知执行..."); }

3. 书写建议

  1. 方法命名尽量规范(如查询方法用 find 开头,更新方法用 update 开头),便于切入点表达式匹配。
  2. 描述切入点时,优先基于接口匹配,而非直接匹配实现类,增强扩展性。
  3. 在满足需求的前提下,尽量缩小切入点的匹配范围,避免不必要的增强。

八、连接点(JoinPoint)

1. 作用

在 Spring 中,JoinPoint抽象了连接点,通过它可以获取方法执行的相关信息,如目标类名、方法名、方法参数等。

2. 类型说明

ProceedingJoinPoint:仅用于@Around通知,是JoinPoint的子类,额外提供了proceed()方法用于执行原始方法。

JoinPoint:可用于@Before、@After、@AfterReturning、@AfterThrowing四种通知。

3. 常用方法示例

例子 1:@Before 使用 JoinPoint

@Aspect @Component @Slf4j public class DemoAspect { /** * 前置通知:目标方法执行之前执行 * 切入点表达式: * execution(* com.sky.mapper.*.*(..)) * 解释: * * 任意返回值 * com.sky.mapper 包名 * * 包下任意类 * * 类中任意方法 * (..) 任意参数 * * 最终效果:拦截 com.sky.mapper 包下 所有类 的 所有方法 */ @Before("execution(* com.sky.mapper.*.*(..))") public void beforeMethod(JoinPoint joinPoint){ // ===================== 1. 获取被拦截的【目标类】信息 ===================== // joinPoint.getTarget():获取被拦截的目标对象(例如:UserMapper的代理对象) Object targetObj = joinPoint.getTarget(); // 获取目标类的全类名(包名 + 类名) String className = targetObj.getClass().getName(); // ===================== 2. 获取被拦截的【方法名】信息 ===================== // joinPoint.getSignature():获取方法签名(包含方法名、修饰符等信息) // getName():获取方法名称 String methodName = joinPoint.getSignature().getName(); // ===================== 3. 获取【方法参数】信息 ===================== // getArgs():获取目标方法的参数列表,返回值是 Object[] 数组 Object[] args = joinPoint.getArgs(); // ===================== 4. 输出日志 ===================== log.info("========== 方法执行前记录信息 =========="); log.info("被拦截的类名:{}", className); // 输出:com.sky.mapper.UserMapper log.info("被拦截的方法名:{}", methodName); // 输出:getById / insert 等 log.info("方法传入的参数:{}", Arrays.toString(args)); // 输出:[102] / [User对象] log.info("====================================="); } }

例子 2:@Around 使用 ProceedingJoinPoint

重点:只有它能调用proceed()执行目标方法

@Around("execution(* com.sky.mapper.*.*(..))") public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable { // 1. 获取信息(和JoinPoint一样) String methodName = pjp.getSignature().getName(); log.info("环绕前:{}", methodName); // 2. 执行目标方法(必须调用!不调用方法不执行) Object result = pjp.proceed(); // 3. 方法执行后 log.info("环绕后:{}", methodName); // 必须返回结果 return result; }

执行顺序

环绕前:getById 目标方法执行 环绕后:getById

JoinPoint 最常用 4 个方法:

// 1. 获取方法参数 Object[] args = joinPoint.getArgs(); // 2. 获取方法名 String methodName = joinPoint.getSignature().getName(); // 3. 获取目标类(被拦截的类) Object target = joinPoint.getTarget(); // 4. 获取目标类名 String className = joinPoint.getTarget().getClass().getName();

九、关键注意事项总结

  1. @Around通知必须调用proceed()方法,否则目标方法不会执行。
  2. @Around方法的返回值类型必须为Object,否则会影响原始方法的返回值。
  3. 切入点表达式可以使用&&、||、!进行逻辑组合,实现复杂匹配条件。
  4. 多切面场景下,优先使用@Order注解控制执行顺序,避免默认排序导致的问题。