【JavaEE28-后端部分】Spring AOP 通知详解——五种“增强时机”,一网打尽

张开发
2026/5/3 3:15:23 15 分钟阅读
【JavaEE28-后端部分】Spring AOP 通知详解——五种“增强时机”,一网打尽
一、演示几种通知类型我们先写两个测试接口:packagecom.zhongge.controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;/** * ClassName TestController * Description TODO aop测试 * Author 笨忠 * Date 2026-04-02 16:12 * Version 1.0 */RequestMapping(/test)RestControllerpublicclassTestController{RequestMapping(/t1)publicIntegert1(){return1;}RequestMapping(/t2)publicBooleant2(){returntrue;}RequestMapping(/t3)publicStringt3(){returnt3;}}packagecom.zhongge.controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;/** * ClassName UserController * Description TODO * Author 笨忠 * Date 2026-04-03 14:48 * Version 1.0 */RequestMapping(/user)RestControllerpublicclassUserController{RequestMapping(/u1)publicStringu1(){returnu1;}RequestMapping(/u2)publicStringu2(){returnu2;}}我们先有一个切面:加两个注解Around环绕通知此注解标注的通知方法在目标方法前后都被执行Before前置通知此注解标注的通知方法在目标方法前被执行After后置通知此注解标注的通知方法在目标方法后被执行无论是否有异常都会执行AfterReturning返回后通知此注解标志的通知方法在目标方法后被执行有异常不会执行AfterThrowing异常后通知此注解标注的通知方法发生异常后执行代码AspectComponentSlf4jpublicclassAspectDemo1{Around(execution(* com.zhongge.controller.*.*(..)))publicObjecttimeRecord(ProceedingJoinPointpjp)throwsThrowable{log.info(目标方法执行前面...);//执行目标方法Objectproceedpjp.proceed();log.info(目标方法执行后面...);returnproceed;}//前置通知Before(execution(* com.zhongge.controller.*.*(..)))publicvoiddoBefore(){log.info(do Before);}//后置通知After(execution(* com.zhongge.controller.*.*(..)))publicvoiddoAfter(){log.info(do After);}//返回后通知AfterReturning(execution(* com.zhongge.controller.*.*(..)))publicvoiddoAfterReturning(){log.info(do AfterReturning);}//异常后通知AfterThrowing(execution(* com.zhongge.controller.*.*(..)))publicvoiddoAfterThrowing(){log.info(do AfterThrowing);}}结果没有异常的时候有异常的时候注意连接点只能在around中使用开篇你收到过快递通知吗老铁们你有没有发现一个现象你在淘宝买个东西快递公司会给你发好多条通知。“您的订单已付款”下单后立即通知“您的包裹已出库”发货前“您的包裹正在派送中”派送前“您的包裹已签收”签收后“您的包裹异常请及时联系”出问题时快递公司在你购物流程的不同阶段给你发送不同的通知。这就是“不同时机做不同的事”。在 Spring AOP 中通知Advice就是这样的“快递通知”——它定义了在目标方法执行的什么阶段执行什么样的增强代码。本期我们就来详细学习 Spring AOP 的五种通知类型并通过代码和图解搞清楚它们的执行时机、执行顺序以及注意事项。二、五种通知类型一张图看懂三、五种通知类型详解生活化3.1 Before 前置通知——进门之前先敲门概念在目标方法执行之前执行。生活例子你去朋友家做客进门之前先敲门。不管朋友在不在家你都会敲门。适用场景日志记录、参数校验、权限检查。代码示例Before(execution(* com.zhongge.controller.*.*(..)))publicvoiddoBefore(){log.info(前置通知方法即将执行先打个招呼);}3.2 After 后置通知——出门之后关灯概念在目标方法执行之后执行无论方法是否抛出异常都会执行。生活例子你离开朋友家出门后随手把门关上。即使你在朋友家吵架了出门后还是要关门。适用场景释放资源、清理临时数据、记录最终结果。代码示例After(execution(* com.zhongge.controller.*.*(..)))publicvoiddoAfter(){log.info(后置通知方法执行完毕不管有没有异常都来收个尾);}3.3 AfterReturning 返回后通知——正常离开时挥手告别概念在目标方法正常返回之后执行。如果方法抛出异常则不会执行。生活例子你在朋友家玩得很开心正常离开时朋友送你到门口挥手告别。如果你和朋友吵架了就不会有这个告别环节。适用场景记录成功日志、缓存更新、返回值处理。代码示例AfterReturning(execution(* com.zhongge.controller.*.*(..)))publicvoiddoAfterReturning(){log.info(返回后通知方法正常返回没有异常可以记录成功日志);}3.4 AfterThrowing 异常后通知——出事了赶紧报警概念在目标方法抛出异常之后执行。如果方法正常返回则不会执行。生活例子你在朋友家不小心打碎了一个花瓶朋友立刻打电话报警。如果一切正常就不会有这个环节。适用场景记录异常日志、发送告警、事务回滚。代码示例AfterThrowing(execution(* com.zhongge.controller.*.*(..)))publicvoiddoAfterThrowing(){log.info(异常后通知方法抛出异常赶紧记录错误日志);}3.5 Around 环绕通知——全程陪同概念在目标方法执行前后都可以执行是最强大的通知类型。它可以控制目标方法是否执行、修改返回值、处理异常。生活例子你请了一个私人导游他全程陪同你进门之前先介绍游玩过程中随时讲解结束后送你离开。他甚至可以决定“今天不去了改天再来”。适用场景性能监控统计耗时、事务管理、权限控制。代码示例Around(execution(* com.zhongge.controller.*.*(..)))publicObjectdoAround(ProceedingJoinPointjoinPoint)throwsThrowable{longstartSystem.currentTimeMillis();log.info(环绕通知开始方法即将执行);ObjectresultjoinPoint.proceed();// 执行目标方法longendSystem.currentTimeMillis();log.info(环绕通知结束方法执行耗时 {} ms,end-start);returnresult;}四、动手实验验证五种通知的执行顺序4.1 创建一个测试切面Slf4jAspectComponentpublicclassAllAdviceDemo{Pointcut(execution(* com.zhongge.controller.*.*(..)))privatevoidpt(){}Before(pt())publicvoidbefore(){log.info(① Before 前置通知);}After(pt())publicvoidafter(){log.info(⑤ After 后置通知最后执行无论异常);}AfterReturning(pt())publicvoidafterReturning(){log.info(④ AfterReturning 返回后通知正常才执行);}AfterThrowing(pt())publicvoidafterThrowing(){log.info(④ AfterThrowing 异常后通知异常才执行);}Around(pt())publicObjectaround(ProceedingJoinPointjoinPoint)throwsThrowable{log.info(② Around 环绕通知开始在 Before 之前);ObjectresultjoinPoint.proceed();log.info(⑥ Around 环绕通知结束在 After 之后);returnresult;}}4.2 创建一个测试控制器RestControllerRequestMapping(/test)publicclassTestController{RequestMapping(/normal)publicStringnormal(){log.info(★★★ 目标方法正常执行 ★★★);return正常返回;}RequestMapping(/error)publicStringerror(){log.info(★★★ 目标方法即将抛出异常 ★★★);inta10/0;// 故意制造异常return不会执行到这里;}}4.3 测试正常情况访问http://localhost:8080/test/normal观察日志② Around 环绕通知开始在 Before 之前 ① Before 前置通知 ★★★ 目标方法正常执行 ★★★ ④ AfterReturning 返回后通知正常才执行 ⑤ After 后置通知最后执行无论异常 ⑥ Around 环绕通知结束在 After 之后执行顺序图解Around开始 → Before → 目标方法 → AfterReturning → After → Around结束4.4 测试异常情况访问http://localhost:8080/test/error观察日志② Around 环绕通知开始在 Before 之前 ① Before 前置通知 ★★★ 目标方法即将抛出异常 ★★★ ④ AfterThrowing 异常后通知异常才执行 ⑤ After 后置通知最后执行无论异常注意AfterReturning没有执行Around中proceed()之后的代码也没有执行。执行顺序图解Around开始 → Before → 目标方法抛出异常 → AfterThrowing → After五、核心知识点总结5.1 执行顺序口诀正常情况阿彪Around先开场小贝Before接着上业务大哥目标方法中间忙阿瑞AfterReturning随后上阿富After最后收场阿彪Around关门谢客。异常情况阿彪Around先开场小贝Before接着上业务大哥目标方法出状况阿富After照常收场阿彪后半段不上场。5.2 各通知的“保命”规则通知类型执行时机异常时是否执行能否阻止目标方法Before目标方法执行前会因为还没执行不能After目标方法执行后会不能AfterReturning目标方法正常返回后不会不能AfterThrowing目标方法抛出异常后只会不能Around包裹整个方法前半段执行后半段不执行能不调用proceed5.3 Around 的特殊之处必须调用joinPoint.proceed()否则目标方法不会执行。返回值必须是Object用来接收目标方法的返回值并返回给调用者。可以控制目标方法是否执行不调用proceed()目标方法就被“短路”了。可以修改返回值拿到proceed()的结果后可以修改再返回。六、Pointcut把重复的切点表达式抽出来一个切面类中有多个切面你有没有发现之前的每个通知都重复写了execution(* com.zhongge.controller.*.*(..))如果切点表达式很长写很多次就太累了。Spring 提供了Pointcut注解可以把切点表达式抽取成一个方法然后其他地方直接引用。6.1 抽取切点Pointcut(execution(* com.zhongge.controller.*.*(..)))privatevoidpt(){}// 方法名随意方法体为空6.2 引用切点Before(pt())publicvoidbefore(){...}After(pt())publicvoidafter(){...}6.3 跨切面类引用如果其他切面类也想用这个切点把private改成public然后通过全限定类名引用Before(com.zhongge.aspect.AspectDemo1.pt())publicvoiddoBefore(){...}七、多个切面的执行顺序Order当有多个切面匹配同一个目标方法时它们的执行顺序是怎样的默认情况下Spring 按照切面类的类名字母顺序排序。比如AspectA和AspectBAspectA的Before先执行After后执行。7.1 使用 Order 手动控制AspectComponentOrder(1)//加载类上publicclassAspectA{...}AspectComponentOrder(2)publicclassAspectB{...}规则Before通知数字越小越先执行。After通知数字越小越后执行因为后置通知是逆向执行。7.2 执行顺序图解Before 执行顺序AspectA(1) → AspectB(2) → 目标方法 After 执行顺序AspectB(2) → AspectA(1)为什么After是反的可以理解为“先进后出”先执行的切面它的After要等到最后才执行就像叠盘子先放的盘子最后才能拿走。最外层优先级最高最里层优先级最低。八、结语记住这三张图就够了图一五种通知的执行位置图二正常 vs 异常情况执行的通知正常Around开始 → Before → 目标方法 → AfterReturning → After → Around结束异常Around开始 → Before → 目标方法抛异常 → AfterThrowing → After图三多切面顺序下一篇预告后续内容干货满满记得点赞关注收藏⭐不迷路下一期我们将学习切点表达式的详细语法包括execution和annotation两种方式以及如何用它们精确匹配你想要增强的方法。敬请期待

更多文章