Spring AOP 五种通知类型详解,别再傻傻分不清了

发布时间:2026/7/2 5:37:02
Spring AOP 五种通知类型详解,别再傻傻分不清了 一、为什么需要 AOP先聊点实际的。我们做后端开发的经常会遇到一些通用逻辑——比如日志记录、权限校验、事务管理、性能监控……这些东西散落在各个业务方法里写起来重复又麻烦还不好维护。AOP面向切面编程就是来解决这个问题的。它能把这些通用逻辑抽出来统一维护然后在需要的地方切进去不用改业务代码就能增强功能。就拿我现在做的登录校验来说有些接口必须登录才能访问总不能在每个 Controller 方法里都写一遍判断登录的代码吧太蠢了。用 AOP 就很优雅——写一个切面加个自定义注解哪个接口需要登录校验就在方法上加个注解完事。一句话理解 AOP在不修改原有业务代码的前提下对方法进行增强。二、Spring AOP 的五种通知类型Spring AOP 一共提供了五种通知类型说白了就是你的切面逻辑在什么时候执行。下面我一个一个来讲结合实际场景说清楚每种通知的特点和用法。1. 前置通知 Before特点切面逻辑在目标方法执行之前执行。这个最好理解就是在方法跑之前先干点啥。比如权限校验——在执行业务方法之前先判断用户有没有登录、有没有权限不符合条件就直接抛异常或者返回根本不让目标方法执行。Before(execution(com.example.service.*.*(..)))publicvoidbeforeAdvice(){System.out.println(目标方法执行前先做权限校验...);}适用场景权限校验、参数校验、日志记录方法入参等。2. 返回通知 AfterReturning特点切面逻辑在目标方法正常返回之后执行。注意这里有个关键词——“正常返回”。也就是说如果目标方法执行过程中抛出了异常这个通知是不会执行的。返回通知还有个好处它能拿到目标方法的返回值。比如你想在方法返回后记录一下返回结果或者对返回值做一些处理用这个就很合适。AfterReturning(pointcutexecution(com.example.service.*.*(..)),returningresult)publicvoidafterReturningAdvice(Objectresult){System.out.println(目标方法正常返回了返回值是result);}适用场景记录方法返回值、对返回结果做后置处理、正常流程的日志记录等。3. 后置通知 After特点切面逻辑在目标方法执行之后执行无论方法是否抛出异常。很多人容易把这个和返回通知搞混。记住一句话After 相当于 finally不管出不出异常都会执行AfterReturning 只有正常返回才执行。后置通知有个局限性——它拿不到异常对象也拿不到返回值。它就是个不管怎样我都要跑一下的通知。After(execution(com.example.service.*.*(..)))publicvoidafterAdvice(){System.out.println(目标方法执行完了不管成功还是失败清理一下资源...);}适用场景资源释放、清理工作、最终的日志记录等。4. 异常通知 AfterThrowing特点切面逻辑在目标方法抛出异常之后执行。和返回通知正好相反——只有出异常了才会走这个通知正常返回是不会走的。异常通知的优势很明显它能捕获到异常对象。你可以拿到异常的类型、信息做一些异常处理或者告警。AfterThrowing(pointcutexecution(com.example.service.*.*(..)),throwingex)publicvoidafterThrowingAdvice(Exceptionex){System.out.println(目标方法抛出异常了ex.getMessage());// 可以做告警、异常日志记录等}适用场景异常监控、告警通知、异常日志记录等。5. 环绕通知 Around特点最强大的通知类型集成了前面四种的功能。可以在目标方法执行前后都做事情还能控制目标方法是否执行、什么时候执行。环绕通知需要接收一个 ProceedingJoinPoint 参数通过调用 proceed() 方法来执行目标方法。如果你不调用 proceed()目标方法根本就不会执行。正因为它太灵活了所以用的时候要格外小心。Around(execution(com.example.service.*.*(..)))publicObjectaroundAdvice(ProceedingJoinPointpjp)throwsThrowable{System.out.println(目标方法执行前...);// 相当于 BeforeObjectresultnull;try{resultpjp.proceed();// 执行目标方法System.out.println(目标方法正常返回...);// 相当于 AfterReturning}catch(Throwablethrowable){System.out.println(目标方法抛出异常...);// 相当于 AfterThrowingthrowthrowable;// 注意异常要继续抛出去}finally{System.out.println(目标方法执行完毕...);// 相当于 After}returnresult;}适用场景性能监控统计方法执行时间、事务管理、需要完全控制方法执行流程的场景。三、五种通知类型对比总结为了方便大家记忆我整理了一张对比表通知类型执行时机能否拿到返回值能否拿到异常Before目标方法执行前❌ 不能❌ 不能AfterReturning目标方法正常返回后✅ 能returning 属性❌ 不能After目标方法执行后无论是否异常❌ 不能❌ 不能AfterThrowing目标方法抛出异常后❌ 不能✅ 能throwing 属性Around环绕目标方法执行前后都可以✅ 能proceed() 返回值✅ 能try-catch 捕获四、切入点表达式怎么选讲完了通知类型再简单说一下切入点表达式。Spring AOP 提供了 9 种切入点表达式但实际常用的就两种execution按方法签名匹配最灵活也最常用。比如匹配某个包下所有类的所有方法。annotation按注解匹配用自定义注解来标记需要切面的方法。这种方式更灵活粒度更细。我现在做的登录校验用的就是 annotation 的方式——写一个 LoginRequired 注解哪个接口需要登录校验就在方法上加这个注解非常方便。// 自定义注解Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)publicinterfaceLoginRequired{}// 切面中使用Around(annotation(com.example.annotation.LoginRequired))publicObjectcheckLogin(ProceedingJoinPointpjp)throwsThrowable{// 登录校验逻辑...returnpjp.proceed();}其他 7 种切入点表达式比如 within、this、target、args 等平时用得不多大家了解一下就行真用到了再去查文档也不迟。五、环绕通知的坑异常别乱吃这里重点说一下环绕通知的一个常见坑——异常处理。很多人写环绕通知的时候喜欢把异常 catch 住然后吃掉比如只打个日志不往外抛。这在单切面场景下可能没啥问题但如果有多个切面比如你的业务切面和事务切面同时存在就会出大问题。举个例子假设你有一个登录校验切面和一个事务切面它们都作用在同一个方法上。如果登录校验切面把异常 catch 住了没有往外抛事务切面就感知不到异常事务就不会回滚——这就导致了事务失效划重点在环绕通知中catch 到异常后一定要继续抛出去不要自己吃掉。除非你明确知道自己在做什么否则不要吞异常。正确的写法应该是这样的Around(annotation(LoginRequired))publicObjectcheckLogin(ProceedingJoinPointpjp)throwsThrowable{// 前置逻辑校验登录checkUserLogin();Objectresult;try{resultpjp.proceed();// 执行目标方法}catch(Throwablethrowable){// 可以记录异常日志但一定要继续抛log.error(方法执行异常,throwable);throwthrowable;// 关键异常继续往外抛}returnresult;}别小看这个细节很多线上的事务失效问题根源就在这里。写在最后Spring AOP 说难不难说简单也不简单。五种通知类型是基础中的基础一定要搞清楚它们的区别和适用场景。希望这篇文章能帮到正在学习 AOP 的你。