【Java踩坑笔记】38_AOP切面失效:同一个类里调用为什么不拦截?

发布时间:2026/7/12 19:11:39
【Java踩坑笔记】38_AOP切面失效:同一个类里调用为什么不拦截? 38 | AOP 切面失效同一个类里调用为什么不拦截摘要AOP 切面的底层是代理同一个类里自调用this.xx()不经过代理切面失效。这和Transactional、Async是同一个问题。一、问题现象ServicepublicclassOrderService{publicvoidcreateOrder(Orderorder){log.info(创建订单);checkPermission(order);// ❌ 自调用切面不生效}PreAuthorize(hasRole(ADMIN))// 权限切面publicvoidcheckPermission(Orderorder){...}}现象checkPermission()上的权限切面不执行没有任何权限检查。二、原理解析2.1 AOP 的本质是代理调用 orderService.createOrder() │ ▼ Spring 容器返回的是代理对象不是 OrderService 本身 │ ▼ 代理对象拦截方法调用 → 执行切面逻辑 → 调用目标方法关键必须通过代理对象调用方法切面才会生效。2.2 自调用不经过代理publicvoidcreateOrder(Orderorder){checkPermission(order);// 等价于 this.checkPermission(order)// this 是目标对象不是代理对象}调用流程 orderService.createOrder() → 经过代理 ✅ └── this.checkPermission() → 不经过代理 ❌切面失效2.3 三种 AOP 失效场景对比注解失效原因解决方案Transactional自调用事务拦截器不执行拆到另一个 ServiceAsync自调用异步拦截器不执行拆到另一个 Service自定义Aspect自调用切面不执行拆到另一个类三、踩坑现场场景日志切面失效AspectComponentpublicclassLogAspect{Around(annotation(com.example.Log))publicObjectlog(ProceedingJoinPointpjp)throwsThrowable{longstartSystem.currentTimeMillis();Objectresultpjp.proceed();log.info(耗时{}ms,System.currentTimeMillis()-start);returnresult;}}ServicepublicclassUserService{LogpublicvoidcreateUser(Useruser){// ...syncToLdap(user);// ❌ 自调用Log 切面不生效}LogpublicvoidsyncToLdap(Useruser){...}}四、正确写法4.1 拆到另一个类推荐// ✅ 正确拆到另一个 ServiceServicepublicclassUserService{AutowiredprivateUserSyncServicesyncService;LogpublicvoidcreateUser(Useruser){// ...syncService.syncToLdap(user);// ✅ 经过代理}}ServicepublicclassUserSyncService{LogpublicvoidsyncToLdap(Useruser){...}}4.2 通过 AopContext 获取代理不推荐// ⚠️ 需要 EnableAspectJAutoProxy(exposeProxy true)ServicepublicclassUserService{publicvoidcreateUser(Useruser){// 通过 AopContext 获取代理对象((UserService)AopContext.currentProxy()).syncToLdap(user);// ✅ 经过代理切面生效}LogpublicvoidsyncToLdap(Useruser){...}}4.3 用 AspectJ 编译期织入终极方案!-- 用 AspectJ不依赖代理自调用也生效 --!-- pom.xml --dependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactId/dependencyAspectJ 在编译期就把切面逻辑织入到字节码里不存在代理问题但配置复杂。五、最佳实践✅ AOP 使用的 3 条铁律自定义切面类不要切同一个类里的内部调用必须自调用时拆到另一个类最简单Transactional、Async也有同样问题要警惕 如何快速发现自调用问题开启 IDEA 的SpringBoot Actuator健康检查或加一个单元测试TestpublicvoidtestTransactionPropagation(){// 验证事务是否生效booleanhasTransactionTransactionSynchronizationManager.getCurrentTransactionName()!null;Assert.assertTrue(hasTransaction);}六、小结AOP 切面的底层是代理对象自调用this.xx()不经过代理切面失效拆到另一个类是最简单的修复方案Transactional、Async也是 AOP 代理有同样问题终极方案用 AspectJ 编译期织入不依赖运行时代理下一篇预告Spring Boot 配置文件加载顺序优先级搞反就完了——application.yml、application-dev.yml、命令行参数到底谁覆盖谁更多资料一线大厂Java面试题合集