Spring AOP 完整教程(中篇)
承接上篇 AOP 基础概念与计时入门案例本篇为进阶核心内容详细讲解 5 类通知执行时机、Pointcut 复用切点、两种切点表达式语法、JoinPoint 连接点 API、多切面执行优先级控制配套完整可运行代码、执行流程对比是面试高频核心考点下篇将结合 ThreadLocal 完成操作日志综合实战。一、五大通知Advice类型全解通知就是切面中封装的增强逻辑根据执行时机分为 5 种各自执行顺序、使用场景差异巨大。1. Around 环绕通知【重中之重企业最常用】执行时机目标方法执行前、执行后都会执行独有特性唯一可以手动控制原始方法是否执行必须调用pjp.proceed()才会执行目标业务方法方法返回值必须是 Object用来接收目标方法返回的数据优势可捕获异常、记录入参、返回值、计算耗时一站式完成日志统计。完整示例代码Slf4j Aspect Component public class TimeAspect { Around(execution(* com.itheima.service.impl.*.*(..))) public Object around(ProceedingJoinPoint pjp) throws Throwable { log.info(【环绕前置】方法即将执行); long start System.currentTimeMillis(); // 执行原始目标方法 Object result pjp.proceed(); long end System.currentTimeMillis(); log.info(【环绕后置】方法执行完成耗时{}ms, end - start); return result; } }2. Before 前置通知目标方法运行之前执行无法拦截、修改返回值不能控制方法是否执行。适用场景打印请求参数、权限校验前置判断。Before(execution(* com.itheima.service.impl.*.*(..))) public void before(JoinPoint jp){ log.info(【前置通知】准备调用方法{},jp.getSignature().getName()); }3. After 后置通知目标方法执行完毕后执行无论正常结束、抛出异常都会执行类似代码块 finally。适用场景统一资源释放、后置收尾打印。4. AfterReturning 返回通知仅当目标方法无异常正常执行完毕才会触发方法抛异常则不会执行。适用场景记录接口正常返回数据。5. AfterThrowing 异常通知只有目标方法抛出异常时才执行正常走完不会触发。适用场景全局异常日志记录、异常告警推送。两种执行顺序演示无异常完整执行顺序Around 前置逻辑 → Before → 目标方法执行 → AfterReturning → After → Around 后置逻辑目标方法抛出异常顺序Around 前置逻辑 → Before → 方法抛异常 → AfterThrowing → After注环绕通知内可手动 try-catch 捕获异常阻断异常向外抛出。二、PointCut 抽取公共切点表达式使用场景项目多处切面复用同一套匹配规则重复书写 execution 表达式冗余使用Pointcut注解抽取统一切点一处定义多处引用。代码示例Slf4j Aspect Component public class TimeAspect { // 抽取公共切点匹配所有业务层方法 Pointcut(execution(* com.itheima.service.impl.*.*(..))) public void servicePoint(){} // 直接引用切点方法名无需重复写表达式 Around(servicePoint()) public Object around(ProceedingJoinPoint pjp) throws Throwable { long start System.currentTimeMillis(); Object res pjp.proceed(); log.info(执行耗时{}ms,System.currentTimeMillis()-start); return res; } Before(servicePoint()) public void before(JoinPoint jp){ log.info(即将执行方法{},jp.getSignature().getName()); } }访问权限说明private 修饰仅当前切面类内部可以引用public 修饰其他切面类也能引用该切点。三、两种主流切入点表达式方式 1execution 方法匹配表达式全量拦截专用根据包、类、方法名、返回值、参数精准匹配方法是入门计时案例使用的表达式。完整标准语法execution(访问修饰符? 返回值 包名.类名.?方法名(参数) throws 异常?)带?代表可省略内容访问修饰符默认不写匹配 public。两大通配符规则*单个任意匹配可匹配返回值、包名、类名、单个参数..多层连续匹配可匹配多级包、任意数量任意参数。常用匹配示例// 匹配itheima下所有service包所有类所有方法 execution(* com.itheima..service.*.*(..)) // 匹配controller下所有save/update/delete开头的增删改接口 execution(* com.itheima.controller.*.save*(..)) || execution(* com.itheima.controller.*.update*(..)) || execution(* com.itheima.controller.*.delete*(..)) // 匹配返回值void、无参数方法 execution(void com.itheima.service.impl.*.*())书写规范建议缩小匹配范围尽量不用..泛匹配全项目减少拦截性能损耗优先匹配接口而非实现类拓展性更强。方式 2annotation 注解匹配按需拦截专用自定义标记注解仅拦截添加该注解的方法适合少量接口单独记录日志场景。完整使用流程自定义标记注解import java.lang.annotation.*; Target(ElementType.METHOD) // 仅作用在方法 Retention(RUNTIME) public interface LogRecord {}Controller 接口添加注解标记PostMapping(/dept/save) LogRecord public Result save(Dept dept){ deptService.save(dept); return Result.success(); }切面切点匹配注解Pointcut(annotation(com.itheima.anno.LogRecord)) public void logPoint(){}两种表达式选型对比批量拦截整个包全部业务方法 → 选用 execution仅少量接口需要增强灵活按需开启 → 选用 annotation。四、JoinPoint 连接点 API 获取方法信息Spring 提供 JoinPoint 对象封装目标方法全部信息不同通知使用对象有区分ProceedingJoinPoint仅 Around 环绕通知可用独有proceed()执行目标方法JoinBefore/After/AfterReturning/AfterThrowing 通用无执行方法 API。通用核心方法两类对象均可调用// 获取目标类完整类名 String className jp.getTarget().getClass().getName(); // 获取方法签名方法名、返回值 Signature sig jp.getSignature(); String methodName sig.getName(); // 获取调用时传入的所有参数 Object[] args jp.getArgs();环绕通知独有方法// 执行原始业务方法必须调用否则目标逻辑不会执行 Object result pjp.proceed();完整代码示例Around(servicePoint()) public Object around(ProceedingJoinPoint pjp) throws Throwable { // 提取方法信息 String className pjp.getTarget().getClass().getName(); String method pjp.getSignature().getName(); Object[] params pjp.getArgs(); log.info(执行类{}方法{}入参{},className,method,params); Object res pjp.proceed(); return res; }五、多切面执行顺序控制项目中存在多个切面类计时切面、日志切面、权限切面同时匹配同一个目标方法时执行顺序有默认规则也可手动自定义优先级。1. 默认排序规则切面类名字母自然排序前置通知字母靠前的切面先执行后置通知字母靠前的切面后执行。2. Order 注解手动控制优先级切面类添加Order(数字)数字越小优先级越高前置逻辑数字小先执行后置 / 异常逻辑数字小后执行。示例Order(1) // 优先级更高前置先执行 Aspect Component public TimeAspect {} Order(10) Aspect Component public LogAspect {}执行流程举例TimeAspectOrder1、LogAspectOrder10无异常场景Time 环绕前置 → Log 环绕前置Time 前置 → Log 前置执行目标方法Log 返回通知 → Time 返回通知Log 后置 → Time 后置Log 环绕后置 → Time 环绕后置中篇全文总结五大通知Around (核心)、Before、After、AfterReturning、AfterThrowing区分有无异常的执行顺序Pointcut 抽取公共切点表达式简化多处重复切点代码两种切点execution 按包 / 方法批量匹配annotation 注解按需拦截JoinPoint 获取类名、方法、参数ProceedingJoinPoint 仅环绕可用可执行目标方法多切面默认按类名字母排序Order 数字越小优先级越高。中篇拓展实操练习编写切面同时实现 5 类通知分别打印日志观察有无异常两种执行顺序使用 Pointcut 抽取 service 切点分别在 Before、Around 复用自定义 Log 注解使用注解切点只拦截新增接口创建两个切面添加 Order 修改执行顺序控制台打印验证。中篇面试高频考点五种通知分别在什么时机执行异常场景执行差异Around 和其他通知最大区别是什么execution 表达式中*和..通配符含义JoinPoint 和 ProceedingJoinPoint 区别、各自使用场景多个切面如何调整执行优先级。