
我们在进行项目开发的时候通常会遇到一种非常烦人的情况 业务代码本来写得好好的结果突然要加一堆“额外操作”。比如方法执行前要打印日志接口调用前要校验权限方法执行时间要统计一下用户操作要记录审计日志出异常了要统一处理数据库事务要自动提交或回滚然后我们的代码就很容易变成这样public void createOrder() { System.out.println(开始记录日志); long start System.currentTimeMillis(); try { System.out.println(开始权限校验); // 核心业务逻辑 System.out.println(创建订单); System.out.println(记录操作日志); } catch (Exception e) { System.out.println(异常处理); } long end System.currentTimeMillis(); System.out.println(方法耗时 (end - start)); }乍一看好像也没啥问题。但是朋友你细品一下这个方法到底是“创建订单”的还是“日志 权限 计时 异常处理大杂烩”的(▽)业务代码直接被各种辅助逻辑包围了主线剧情还没开始配角已经库库登场了。这个时候AOP 就出来救场了。AOP 到底是个啥玩意AOP全称是 Aspect Oriented Programming中文叫 面向切面编程。听起来是不是有点抽象没事我们先不看这个高大上的名字咱们先用生活里的例子来理解。假设我们去坐地铁真正的目标是啥当然是从 A 地到 B 地。但是在坐地铁之前我们还要经历- 安检- 刷卡- 进闸机- 出站再刷一次卡这些流程并不是“坐地铁”这个核心动作本身但它们又必须发生在坐地铁的前后。这就很像我们项目里的业务方法。比如 createOrder() 的核心目标是创建订单但它周围可能还需要- 权限校验- 日志记录- 事务控制- 性能统计- 异常处理这些东西就像“地铁安检”和“刷卡”一样它们不是核心业务但经常要围绕着核心业务执行。AOP 的思想就是把这些散落在各个业务方法里的公共逻辑抽出来统一放到一个地方管理然后在合适的时机自动插进去执行。通俗点说就是业务代码只管业务杂活交给 AOP 去整。专业一点说就是AOP 是一种编程思想它通过预编译或运行期间动态代理的方式在不修改原有业务代码的情况下对程序功能进行增强。是不是稍微有感觉了捏为啥我们需要 AOP我们先来看一个没有 AOP 的场景。假设项目里有三个业务方法public void createOrder() { System.out.println(权限校验); System.out.println(创建订单); System.out.println(记录日志); } public void payOrder() { System.out.println(权限校验); System.out.println(支付订单); System.out.println(记录日志); } public void cancelOrder() { System.out.println(权限校验); System.out.println(取消订单); System.out.println(记录日志); }朋友们这代码是不是已经开始有味儿了每个方法里都要写一遍权限校验和日志记录。这会带来几个问题- 代码重复越写越烦- 改一处逻辑要到处找- 容易漏写比如某个方法忘了加权限- 业务逻辑不纯净看起来乱糟糟如果后面产品说“我们日志格式要改一下哈。”那我们就得一个方法一个方法去改。这时候你可能会想我只是一个写业务的为什么要被这些重复代码折磨这合理吗不合理啊朋友所以 AOP 的价值就来了。它可以把这些公共逻辑抽成一个“切面”让业务方法保持干净。比如我们希望业务代码变成这样public void createOrder() { System.out.println(创建订单); } public void payOrder() { System.out.println(支付订单); } public void cancelOrder() { System.out.println(取消订单); }然后日志、权限、耗时统计这些东西全部交给 AOP 自动处理。这就很舒服了嘛业务代码瞬间清爽不少。AOP 里面几个核心概念咱慢慢瞅AOP 里有几个常见概念刚开始看可能有点绕。别慌我们一个一个拆。切面 Aspect切面可以理解成一个“功能模块”。比如- 日志切面- 权限切面- 事务切面- 缓存切面- 接口限流切面它专门处理某一类公共逻辑。比如日志切面就专门负责记录日志。Aspect Component public class LogAspect { }这里的 Aspect 就是在告诉 Spring兄弟我这个类不是普通类我是一个切面。Component 是让 Spring 把它交给容器管理。注意如果只写了 Aspect但是没写 ComponentSpring 可能压根不会管它这个坑挺鸡贼的。连接点 Join Point连接点可以理解成“程序执行过程中的某个位置”。比如- 方法调用前- 方法调用后- 方法抛异常时- 方法正常返回时在 Spring AOP 里面连接点通常就是“方法执行”。比如下面这个方法public void createOrder() { System.out.println(创建订单); }这个 createOrder() 方法的执行过程就是一个连接点。简单说只要是 AOP 有机会插一脚的地方都可以理解成连接点。切点 Pointcut连接点很多但我们不可能每个方法都增强。比如项目里有 1000 个方法我们只想给 service 包下的方法加日志。那咋办这时候就需要切点。切点就是用来筛选连接点的规则。比如Pointcut(execution(* com.example.service.*.*(..))) public void serviceMethods() { }这段意思是匹配 com.example.service 包下所有类的所有方法。我们先不用死磕这个表达式先知道它是“匹配规则”就行。有点像我们用筛子筛东西- 连接点是一大堆沙子- 切点是筛子- 被筛出来的就是我们要增强的方法通知 Advice通知就是“具体要增强的逻辑”。比如- 方法执行前做点事前置通知- 方法执行后做点事后置通知- 方法返回后做点事返回通知- 方法抛异常后做点事异常通知- 方法前后都包起来环绕通知常见注解有这些Before After AfterReturning AfterThrowing Around其中最猛的是 Around。它可以控制方法执行前、执行后甚至可以决定原方法到底要不要执行。不过也正因为它能力太强用的时候要小心点别一不小心把业务方法给“拦腰截断”了。我们来整一个日志 AOP小试牛刀光说不练假把式我们直接整一个最常见的日志切面。假设我们有一个订单业务类Service public class OrderService { public void createOrder() { System.out.println(正在创建订单...); } public void payOrder() { System.out.println(正在支付订单...); } }现在我们想在每个业务方法执行前后都打印日志。如果不用 AOP可能就得这样写public void createOrder() { System.out.println(方法开始执行); System.out.println(正在创建订单...); System.out.println(方法执行结束); }方法少还好方法多了直接裂开。所以我们用 AOP 来整。Aspect Component public class LogAspect { Pointcut(execution(* com.example.service.*.*(..))) public void serviceMethods() { } Before(serviceMethods()) public void before() { System.out.println(方法开始执行); } After(serviceMethods()) public void after() { System.out.println(方法执行结束); } }这样一来只要调用 com.example.service 包下的方法都会自动执行日志逻辑。比如调用orderService.createOrder();控制台大概会输出方法开始执行正在创建订单...方法执行结束这不就优雅起来了嘛。业务代码里没写日志但日志自动出现了。这就是 AOP 的魅力。环绕通知AOP 里的“全能选手”如果我们想统计方法耗时用 Before 和 After 也能做但有点别扭。更适合用 Around。Aspect Component public class TimeCostAspect { Around(execution(* com.example.service.*.*(..))) public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable { long start System.currentTimeMillis(); Object result joinPoint.proceed(); long end System.currentTimeMillis(); System.out.println(joinPoint.getSignature().getName() 方法耗时 (end - start) ms); return result; } }这里有个非常关键的点Object result joinPoint.proceed();这行代码表示执行原本的业务方法。注意如果你忘了写 joinPoint.proceed()原方法就不会执行。这就很离谱了。比如你调用创建订单接口结果 AOP 拦住了但是没放行。最后订单没创建日志还显示“方法执行完了”。这场面就很抽象。所以使用 Around 的时候一定要记住joinPoint.proceed() 就像闸机放行按钮不按它人就过不去。AOP 在项目里能怎么玩AOP 不是只能打印日志它在真实项目里能玩的地方可多了。我们来瞅几个比较实用的场景。场景一统一日志记录比如我们想记录用户调用了哪个接口、传了什么参数、返回了什么结果。可以用 AOP 统一处理。Around(execution(* com.example.controller.*.*(..))) public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println(请求方法 joinPoint.getSignature().getName()); Object[] args joinPoint.getArgs(); System.out.println(请求参数 Arrays.toString(args)); Object result joinPoint.proceed(); System.out.println(返回结果 result); return result; }这样 Controller 里面就不用每个接口都手动写日志了。不过这里有个坑要提前说一下。注意生产环境不要随便打印所有请求参数和返回结果。为啥因为可能会把这些东西打到日志里- 密码- token- 身份证号- 手机号- 银行卡信息- 其他敏感数据到时候日志系统一查嚯敏感信息全裸奔了这可不是闹着玩的。正确做法是- 对敏感字段脱敏- 大对象不要完整打印- 文件流、图片流不要打印- 日志级别要控制好比如密码可以处理成这样password******这样才比较稳。场景二接口防重复提交有些接口不能被用户猛猛点。比如- 创建订单- 支付订单- 提交表单- 领取优惠券如果用户手速快或者网络卡顿疯狂点按钮就可能重复提交。我们可以自定义一个注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface NoRepeatSubmit { int expireSeconds() default 5; }然后在需要防重复提交的方法上使用NoRepeatSubmit(expireSeconds 5) PostMapping(/order/create) public String createOrder() { return 创建订单成功; }接着用 AOP 拦截这个注解。Around(annotation(noRepeatSubmit)) public Object checkRepeatSubmit( ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit ) throws Throwable { String key repeat_submit: joinPoint.getSignature().toShortString(); Boolean success redisTemplate.opsForValue() .setIfAbsent(key, 1, noRepeatSubmit.expireSeconds(), TimeUnit.SECONDS); if (Boolean.FALSE.equals(success)) { throw new RuntimeException(请勿重复提交); } return joinPoint.proceed(); }这样只要方法上加了 NoRepeatSubmit就自动拥有防重复提交能力。是不是有点像给方法贴了个“防连点护盾”嘿嘿。不过这个例子还比较简化真实项目里 key 不能只用方法名。更合理的 key 应该包含- 用户 ID- 请求路径- 请求参数摘要- 业务唯一标识比如repeat_submit:userId:path:requestHash注意防重复提交的 key 设计非常重要设计得太粗会误伤用户设计得太细又防不住重复提交。比如只用接口路径当 key那 A 用户提交了B 用户也可能被拦住。这就属于误伤友军了。场景三权限校验有些接口只有管理员能访问。我们可以定义一个权限注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequirePermission { String value(); }使用方式RequirePermission(order:delete) DeleteMapping(/order/{id}) public String deleteOrder(PathVariable Long id) { return 删除订单成功; }AOP 里面统一校验Around(annotation(requirePermission)) public Object checkPermission( ProceedingJoinPoint joinPoint, RequirePermission requirePermission ) throws Throwable { String permission requirePermission.value(); boolean hasPermission currentUserHasPermission(permission); if (!hasPermission) { throw new RuntimeException(没有权限访问); } return joinPoint.proceed(); } private boolean currentUserHasPermission(String permission) { // 这里一般会从登录用户信息里判断权限 return true; }这样权限逻辑就不用散落在每个 Controller 方法里。看起来是不是清爽多了不过权限这块也有个坑。注意权限校验属于安全边界千万不要只在前端判断后端必须校验。前端按钮隐藏只能防君子防不了会抓包的大佬。后端不校验的话人家直接请求接口按钮有没有根本不重要。场景四操作审计日志有些系统需要记录用户做了什么操作。比如后台管理系统里- 谁删除了订单- 谁修改了用户信息- 谁导出了数据- 谁调整了权限这些都可以用 AOP 搞定。我们先定义一个注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface OperationLog { String value(); }使用OperationLog(删除订单) DeleteMapping(/order/{id}) public String deleteOrder(PathVariable Long id) { return 删除订单成功; }切面Around(annotation(operationLog)) public Object saveOperationLog( ProceedingJoinPoint joinPoint, OperationLog operationLog ) throws Throwable { long start System.currentTimeMillis(); try { Object result joinPoint.proceed(); System.out.println(操作名称 operationLog.value()); System.out.println(执行结果成功); System.out.println(耗时 (System.currentTimeMillis() - start) ms); return result; } catch (Throwable e) { System.out.println(操作名称 operationLog.value()); System.out.println(执行结果失败); System.out.println(失败原因 e.getMessage()); throw e; } }这样我们只需要在方法上加一个注解审计日志就自动记录。这就很适合后台系统、金融系统、权限系统这类场景。不过也要注意注意审计日志最好不要只打印到控制台真实项目里一般要落库或者写入日志系统。不然服务一重启日志找不到了那就有点尴尬。场景五统一事务管理Spring 里我们经常用这个注解Transactional public void createOrder() { // 创建订单 // 扣减库存 // 扣减余额 }其实事务管理也可以理解成 AOP 的经典应用。方法执行前开启事务。方法执行成功后提交事务。方法执行异常后回滚事务。我们写业务的时候只需要加一个 Transactional不用自己手动写try { 开启事务(); 执行业务(); 提交事务(); } catch (Exception e) { 回滚事务(); }Spring 帮我们用 AOP 在背后默默干活。是不是突然感觉 Transactional 也没那么神秘了它大概就是在说你安心写业务事务这块我来兜底。当然Transactional 也有一些经典坑点。注意同一个类内部方法互相调用事务可能不生效。比如Service public class OrderService { public void create() { this.saveOrder(); } Transactional public void saveOrder() { System.out.println(保存订单); } }这里 create() 调用 this.saveOrder()事务可能不会生效。因为 Spring AOP 通常是基于代理对象实现的内部 this 调用绕过了代理。这就像你本来应该从地铁闸机过结果你从员工通道绕过去了那刷卡逻辑自然就没触发。解决思路一般有几种- 把 saveOrder() 放到另一个 Service 里调用- 从 Spring 容器里拿代理对象调用- 使用 AspectJ 这类更底层的方式如果是新手朋友最简单稳妥的方式就是需要事务的方法尽量通过另一个 Spring Bean 来调用不要自己 this.xxx() 调自己。AOP 是怎么实现的我们前面一直在说 AOP 可以“插入逻辑”。那它到底是怎么插进去的Spring AOP 最常见的实现方式是 动态代理。这个词听着有点唬人我们还是用生活例子解释。假设你要租房本来可以直接找房东。但是现在中间来了个中介。你跟中介说“我要租房。”中介先做一些事情- 看你预算- 给你介绍房源- 带你看房- 帮你签合同然后真正的房东才出现。这个中介就像代理对象。你以为你调用的是原始对象其实你调用的是代理对象。代理对象可以在调用原始方法之前和之后加点操作。大概像这样public class OrderServiceProxy { private OrderService orderService; public void createOrder() { System.out.println(方法执行前记录日志); orderService.createOrder(); System.out.println(方法执行后记录日志); } }Spring 并不希望我们手写这些代理类因为太麻烦。所以它会自动帮我们生成代理对象。常见方式有两种┌──────────────┬──────────────────────┬────────────────┐│ 方式 说明 适用情况 │├──────────────┼──────────────────────┼────────────────┤│ JDK 动态代理 基于接口生成代理 类实现了接口 │├──────────────┼──────────────────────┼────────────────┤│ CGLIB 代理 基于继承生成子类代理 类没有实现接口 │└──────────────┴──────────────────────┴────────────────┘如果你的 Service 实现了接口Spring 可能用 JDK 动态代理。如果没实现接口Spring 可能用 CGLIB。不过现在我们日常开发里一般不用太纠结这个知道 Spring 背后是通过代理对象增强方法就可以了。注意因为 Spring AOP 依赖代理所以 private 方法、final 方法、同类内部调用这些场景都可能导致 AOP 不生效。这个坑很常见朋友们一定要记一下。常见切点表达式怎么写切点表达式一开始看会觉得像天书。比如execution(* com.example.service.*.*(..))我们拆开看execution(返回值类型 包名.类名.方法名(参数))上面那个表达式表示匹配 com.example.service 包下任意类的任意方法参数不限返回值不限几个常见写法// 匹配 service 包下所有类的所有方法 Pointcut(execution(* com.example.service.*.*(..))) // 匹配 service 包及其子包下所有类的所有方法 Pointcut(execution(* com.example.service..*.*(..))) // 匹配所有以 get 开头的方法 Pointcut(execution(* com.example..*.get*(..))) // 匹配带有某个注解的方法 Pointcut(annotation(com.example.annotation.OperationLog)) // 匹配某个类里的所有方法 Pointcut(within(com.example.service.OrderService))这里有个小细节com.example.service.*.*只匹配 service 这一层。而com.example.service..*.*两个点 .. 会匹配子包。注意* 和 .. 很容易写错切点范围写大了可能会把不该拦的方法也拦住。比如你本来只想拦 Controller结果把整个项目都拦了。那日志直接刷屏CPU 也跟着猛猛干活排查起来很快乐快乐到想下班。AOP 使用的时候有哪些坑AOP 好用是好用但不是银弹。我们来瞅几个常见坑提前避雷坑一同类内部调用不生效这个前面说过了因为绕过了代理。public void a() { this.b(); } OperationLog(执行 b 方法) public void b() { }如果 a() 调用 this.b()b() 上的 AOP 可能不会触发。解决方式- 拆到另一个 Bean- 使用代理对象调用- 调整代码结构新手朋友优先选拆 Bean简单直接不拐弯。坑二private 方法拦不到Spring AOP 是基于代理的。private 方法外部访问不到代理对象也不好拦。OperationLog(私有方法) private void saveLog() { }这种一般不会生效。所以需要 AOP 增强的方法尽量用 public。坑三切点范围太大比如你写了Around(execution(* com.example..*.*(..)))这表示整个 com.example 下所有方法都可能被拦。看着很爽一把梭。但可能会导致- 日志太多- 性能下降- 拦截到框架内部方法- 出现循环调用- 排查问题困难注意切点范围要尽量精确不要为了省事直接全项目乱怼。坑四环绕通知忘记返回结果比如Around(execution(* com.example.service.*.*(..))) public Object around(ProceedingJoinPoint joinPoint) throws Throwable { joinPoint.proceed(); return null; }如果原方法本来有返回值你这里直接 return null调用方可能直接懵。正确写法Around(execution(* com.example.service.*.*(..))) public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Object result joinPoint.proceed(); return result; }注意环绕通知一定要把原方法返回值正常返回出去除非你明确就是要改返回结果。坑五异常被吞掉有些朋友可能会这么写Around(execution(* com.example.service.*.*(..))) public Object around(ProceedingJoinPoint joinPoint) { try { return joinPoint.proceed(); } catch (Throwable e) { System.out.println(异常 e.getMessage()); return null; } }这样看起来好像处理了异常。但其实问题很大。因为异常被吞掉了外层根本不知道发生了错误。如果这里涉及事务异常被吞之后事务可能也不会正常回滚。正确做法通常是记录日志后继续抛出Around(execution(* com.example.service.*.*(..))) public Object around(ProceedingJoinPoint joinPoint) throws Throwable { try { return joinPoint.proceed(); } catch (Throwable e) { System.out.println(异常 e.getMessage()); throw e; } }注意不要随便吞异常尤其是涉及事务、支付、订单这种核心业务的时候。异常不是坏人它是在告诉我们“兄弟这里出事了。”你把它嘴捂住了后面问题只会更大。AOP 和拦截器、过滤器有啥区别很多朋友会问▎ AOP、Interceptor、Filter 好像都能拦请求它们有啥区别这个问题问得好。我们简单对比一下┌────────────────────┬─────────────────────────────────────│ 名称 作用范围 常见用途├────────────────────┼─────────────────────────────────────│ Filter 过滤器 Servlet 请求级别 编码处理、跨域、请求包装├────────────────────┼─────────────────────────────────────│ Interceptor 拦截器 Spring MVC 请求级别 登录校验、接口权限、请求日志├────────────────────┼─────────────────────────────────────│ AOP 切面 方法级别 日志、事务、权限、审计、耗时统计└────────────────────┴─────────────────────────────────────如果拿进饭店吃饭来类比- Filter 像饭店门口保安所有进门的人都先过它- Interceptor 像服务员进入某个包间前帮你确认预约- AOP 像后厨里的监督员可以盯着某个厨师做某道菜的过程它们都能“拦”但是站的位置不一样。如果你是处理 Web 请求入口比如跨域、编码、请求包装用 Filter 比较合适。如果你是处理 Controller 请求比如登录校验、路径权限用 Interceptor 很合适。如果你是处理某个方法的增强比如操作日志、事务、耗时统计用 AOP 更舒服。当然真实项目里不是非黑即白有些场景几种都能做。我们的选择原则可以简单记成越靠近 HTTP 请求入口用 Filter / Interceptor越靠近业务方法增强用 AOP。一个比较完整的项目案例用 AOP 实现操作日志我们来整一个比较完整的例子。目标是在需要记录日志的方法上加 OperationLog然后自动记录操作名称、方法名、参数、耗时、执行结果。定义注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface OperationLog { String value(); }这个注解只能放在方法上。value() 用来写操作名称。在接口上使用注解RestController RequestMapping(/order) public class OrderController { OperationLog(创建订单) PostMapping(/create) public String createOrder(String productName) { return 创建订单成功 productName; } OperationLog(取消订单) PostMapping(/cancel) public String cancelOrder(Long orderId) { return 取消订单成功 orderId; } }现在我们只要给方法贴上 OperationLog它就应该自动记录日志。编写切面Aspect Component public class OperationLogAspect { Around(annotation(operationLog)) public Object recordLog( ProceedingJoinPoint joinPoint, OperationLog operationLog ) throws Throwable { long start System.currentTimeMillis(); String methodName joinPoint.getSignature().getName(); Object[] args joinPoint.getArgs(); try { Object result joinPoint.proceed(); long cost System.currentTimeMillis() - start; System.out.println(操作名称 operationLog.value()); System.out.println(方法名称 methodName); System.out.println(请求参数 Arrays.toString(args)); System.out.println(执行耗时 cost ms); System.out.println(执行结果成功); return result; } catch (Throwable e) { long cost System.currentTimeMillis() - start; System.out.println(操作名称 operationLog.value()); System.out.println(方法名称 methodName); System.out.println(请求参数 Arrays.toString(args)); System.out.println(执行耗时 cost ms); System.out.println(执行结果失败); System.out.println(失败原因 e.getMessage()); throw e; } } }这样我们就完成了一个简单版操作日志 AOP。不过生产环境里不建议直接 System.out.println()。我们一般会使用日志框架比如private static final Logger log LoggerFactory.getLogger(OperationLogAspect.class);然后log.info(操作名称{}, operationLog.value()); 如果要落库就可以把这些数据封装成对象然后保存到数据库。 比如 OperationLogRecord record new OperationLogRecord(); record.setOperationName(operationLog.value()); record.setMethodName(methodName); record.setCostTime(cost); record.setSuccess(true);再调用保存方法operationLogService.save(record);注意如果在日志切面里调用保存日志的方法也可能再次触发 AOP造成循环调用。比如你的切点写得太大把 operationLogService.save() 也拦了那就可能出现记录日志 - 保存日志 - 又记录日志 - 又保存日志 - ...这就像两面镜子互相照直接无限套娃。解决方式也简单- 切点范围写精确一点- 排除日志保存相关的类- 日志保存方法不要加对应注解- 单独放到不被切点匹配的包里啥时候适合用 AOP啥时候别用AOP 虽然香但不能啥都往里面塞。适合用 AOP 的场景一般有几个特点- 多个地方都需要同样逻辑- 这段逻辑和核心业务关系不大- 希望业务代码保持干净- 可以通过方法、注解、包路径明确匹配比如- 日志记录- 权限校验- 事务控制- 缓存处理- 限流防刷- 防重复提交- 操作审计- 方法耗时统计这些都很适合。但下面这些情况就不太适合- 业务流程本身的一部分- 逻辑非常复杂且强依赖业务上下文- 执行顺序非常敏感- 隐藏起来反而让代码难理解比如订单状态流转待支付 - 已支付 - 待发货 - 已发货这种核心业务流程就不要强行塞进 AOP。不然代码看起来很干净但新人接手项目时会很痛苦明明方法里没写这些逻辑它到底从哪冒出来的AOP 最大的问题就是“隐式”。它的增强逻辑不在业务方法里直接出现。所以我们要用它解决横切关注点而不是把业务逻辑藏起来。简单记一句公共增强用 AOP核心业务别乱藏。小总结我们今天聊了 AOP 的思想以及它在项目中的一些巧妙用法。AOP 的核心思想其实很简单把和核心业务无关、但很多地方都需要的公共逻辑抽出来统一管理自动织入。它可以让我们的代码从这样权限校验();记录日志();开启事务();执行业务();提交事务();记录结果();变成这样执行业务();其他公共逻辑交给切面处理。这样业务代码更清爽公共逻辑也更好维护。不过朋友们也要记住AOP 不是万能胶。它适合处理日志、权限、事务、审计、耗时统计这类横切逻辑。但不要把核心业务逻辑偷偷藏进 AOP 里不然项目后期排查问题的时候可能会出现一种经典场面我是谁我在哪这个逻辑到底谁执行的这就很折磨了哈。所以我们用 AOP 的时候记住几个原则- 切点范围要精确不要一把梭全项目- 环绕通知要记得调用 joinPoint.proceed()- 有返回值的方法要正常返回结果- 异常不要随便吞掉- 敏感信息不要直接打日志- 核心业务逻辑不要藏进切面里掌握好这些点AOP 在项目里真的能帮我们省很多重复代码也能让代码结构变得更优雅。以上是个人的一些经验分享如果有哪里有什么错误的地方也请大佬们指出。