网站建设和维护工作,平台建设网站公司,营销助手app下载,做美食视频网站目录
Spring AOP 超详细入门教程#xff1a;从概念到源码
写给新手的话
1. AOP基础概念#xff08;先理解思想#xff09;
1.1 什么是AOP#xff1f;#xff08;生活化理解#xff09;
1.2 AOP核心术语#xff08;必须掌握#xff09;
2. Spring AOP快速入门从概念到源码写给新手的话1. AOP基础概念先理解思想1.1 什么是AOP生活化理解1.2 AOP核心术语必须掌握2. Spring AOP快速入门第一个完整例子2.1 添加依赖项目基础2.2 创建业务Controller被增强的目标2.3 创建切面类这是重点2.4 运行测试看效果3. Spring AOP核心概念详解代码级深扒3.1 切点(Pointcut) - 规则定义器3.2 连接点(Join Point) - 符合条件的具体方法3.3 通知(Advice) - 增强的具体逻辑3.4 切面(Aspect) - 切点 通知的集合4. 通知执行顺序代码验证结论4.1 同一切面内的执行顺序4.2 多个切面的执行顺序Order注解5. 切点表达式详解精确匹配5.1 execution表达式最常用5.2 annotation表达式注解匹配6. Spring AOP实现原理源码级理解6.1 代理模式基础先理解设计模式6.1.1 静态代理手动写代理类6.1.2 动态代理自动生成代理类6.2 Spring AOP如何选择代理方式源码逻辑6.3 代理执行流程方法调用时发生了什么7. 完整项目实战整合所有知识点7.1 项目结构7.2 完整代码带全套注释7.3 测试结果分析结论与代码对应8. 常见问题与解决方案代码级避坑8.1 同一个类内方法调用AOP失效8.2 通知中抛出异常导致原方法无法执行8.3 切点表达式过于宽泛影响性能9. 总结知识地图9.1 核心概念关系图9.2 代理方式选择决策树9.3 最佳实践清单10. 最后的叮嘱给新手的建议Spring AOP 超详细入门教程从概念到源码写给新手的话你好我是你的Java学习伙伴。这份教程将用最白话的语言、最详细的代码注释带你彻底搞懂Spring AOP。每个概念我们都会先讲是什么再看代码怎么写最后分析代码如何体现出这个概念。1. AOP基础概念先理解思想1.1 什么是AOP生活化理解AOP 面向切面编程听起来很抽象对吧我们来举几个生活中的例子例子1餐厅服务员你点完菜后服务员会前后都要说您好/请慢用环绕通知上菜前检查餐具是否干净前置通知上菜后主动询问是否需要加调料后置通知如果菜品有问题立即处理投诉异常通知这些额外服务不干扰厨师做菜但增强了用餐体验。这就是AOP思想例子2手机壳手机核心业务能打电话、发短信手机壳切面提供防摔、美观功能你不需要改造手机内部电路就能增强功能1.2 AOP核心术语必须掌握术语通俗解释代码中的体现切点(Pointcut)哪些方法要增强的规则Around(execution(* com.example.*.*(..)))连接点(Join Point)符合规则的具体方法BookController.addBook()通知(Advice)增强什么功能的代码recordTime()方法里的计时逻辑切面(Aspect)切点 通知的组合整个TimeAspect类织入(Weaving)把增强代码织到原方法上Spring自动完成的代理过程2. Spring AOP快速入门第一个完整例子2.1 添加依赖项目基础!-- pom.xml文件 -- !-- spring-boot-starter-aopSpring Boot提供的AOP启动器包含了所有AOP相关依赖 -- !-- 添加后无需任何配置Spring Boot会自动开启AOP功能 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency依赖如何体现AOP思想这个依赖就像餐厅服务员培训手册告诉Spring你要准备好提供额外服务的能力它内部包含了spring-aop、aspectjweaver等核心库相当于服务员的工具包2.2 创建业务Controller被增强的目标// BookController.java // 这个类就是厨师只关心核心业务做菜/处理图书 import org.springframework.web.bind.annotation.RequestMapping; // Spring MVC的请求映射注解把HTTP请求映射到方法 import org.springframework.web.bind.annotation.RestController; // 标识这是RESTful控制器返回JSON数据 RequestMapping(/book) // 所有请求路径的前缀都是/book RestController // 告诉Spring这个类是控制器所有方法返回的数据直接写给客户端相当于Controller ResponseBody public class BookController { // 处理添加图书的请求 RequestMapping(/addBook) // 映射/book/addBook请求 public String addBook() { // 模拟业务逻辑耗时 try { Thread.sleep(100); // 让当前线程暂停100毫秒模拟数据库操作耗时 } catch (InterruptedException e) { e.printStackTrace(); // 打印异常堆栈信息 } return 添加成功; // 返回给客户端的字符串 } // 处理查询图书的请求 RequestMapping(/queryBookById) // 映射/book/queryBookById请求 public String queryBookById() { // 模拟业务逻辑耗时 try { Thread.sleep(200); // 模拟更复杂的数据库查询耗时200毫秒 } catch (InterruptedException e) { e.printStackTrace(); } return 查询成功; } }代码如何体现连接点addBook()和queryBookById()就是连接点因为它们是具体的、可以被AOP控制的方法它们符合execution(* com.example.demo.controller.*.*(..))这个规则所以会被增强2.3 创建切面类这是重点// TimeAspect.java // 这个类就是服务员提供计时服务不修改厨师的做菜逻辑 import lombok.extern.slf4j.Slf4j; // Lombok提供的日志注解自动生成log对象 import org.aspectj.lang.ProceedingJoinPoint; // 连接点对象封装了目标方法的信息和执行能力 import org.aspectj.lang.annotation.Around; // 环绕通知注解表示在目标方法前后都要执行 import org.aspectj.lang.annotation.Aspect; // 标识这是一个切面类Spring看到这个注解就知道要做AOP了 import org.springframework.stereotype.Component; // Spring组件注解让Spring管理这个Bean的生命周期 Slf4j // Lombok注解自动生成private static final Logger log LoggerFactory.getLogger(TimeAspect.class); Aspect // 标识这是切面类核心注解没有它就不是切面 Component // 把切面类交给Spring容器管理这样Spring才能识别并启用它 public class TimeAspect { /** * 记录方法耗时环绕通知 * * param pjp 连接点对象可以通过它 * - 获取方法名pjp.getSignature().getName() * - 获取类名pjp.getTarget().getClass().getName() * - 执行原方法pjp.proceed() * - 获取方法参数pjp.getArgs() * * return 目标方法的执行结果必须返回否则调用者拿不到返回值 * throws Throwable 可能抛出的异常必须声明因为pjp.proceed()可能抛出任何异常 */ // Around环绕通知像三明治一样包裹目标方法 // execution(* com.example.demo.controller.*.*(..))切点表达式匹配controller包下所有类的所有方法 // * 任意返回值 // com.example.demo.controller.*controller包下的所有类 // .*所有方法 // (..)任意参数 Around(execution(* com.example.demo.controller.*.*(..))) public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { // 目标方法执行前 long begin System.currentTimeMillis(); // 记录开始时间毫秒 // 执行目标方法 // pjp.proceed()调用原始方法如addBook() // 这个方法会暂停当前线程直到目标方法执行完毕 // 返回值就是目标方法的返回值如添加成功 Object result pjp.proceed(); // 目标方法执行后 long end System.currentTimeMillis(); // 记录结束时间 // 打印日志方法签名 耗时 // pjp.getSignature()获取方法签名包含方法名、参数类型等 // end - begin计算耗时 log.info(pjp.getSignature() 执行耗时:{}ms, end - begin); // 必须返回结果否则调用方如浏览器收不到返回值 return result; } }代码如何体现切面概念整个TimeAspect类就是一个切面因为它有Aspect注解标识身份包含了切点Around后面的字符串包含了通知recordTime方法体这三者的组合 切面代码如何体现无侵入性注意BookController里完全没有关于计时的代码计时逻辑完全在TimeAspect里这就是不修改源代码就能增强功能完美体现AOP的核心价值2.4 运行测试看效果// 启动Spring Boot应用后访问 // http://localhost:8080/book/addBook // http://localhost:8080/book/queryBookById // 控制台输出示例 // 2023-10-05 14:30:22.123 INFO 12345 --- [nio-8080-exec-1] c.e.d.a.TimeAspect: String addBook() 执行耗时:105ms // 2023-10-05 14:30:22.328 INFO 12345 --- [nio-8080-exec-2] c.e.d.a.TimeAspect: String queryBookById() 执行耗时:203ms如何从输出验证AOP生效c.e.d.a.TimeAspect说明日志来自我们的切面类addBook()成功获取到方法名105ms记录了真实耗时100ms sleep 5ms代码执行没有修改BookController任何一行代码却有了计时功能3. Spring AOP核心概念详解代码级深扒3.1 切点(Pointcut) - 规则定义器概念定义哪些方法要被增强的规则使用AspectJ表达式描述。// 切点表达式详解execution(* com.example.demo.controller.*.*(..)) // 语法结构execution(访问修饰符 返回值 包名.类名.方法名(参数) 异常) // 实际示例execution(public String com.example.demo.controller.BookController.addBook(BookInfo)) // 通配符说明 // * : 匹配任意单个元素返回值、包名、类名、方法名 // .. : 匹配任意多个连续的任意符号包层级、参数个数和类型 // 更多示例 Around(execution(public * com.example.demo.controller.*.*(..))) // 匹配public方法 Around(execution(* com.example.demo.controller.BookController.*(..))) // 只匹配BookController类 Around(execution(* com..controller.*.*(..))) // 匹配com包及其子包下的controller包 Around(execution(* *.*(..))) // 危险匹配所有方法性能差如何从代码中看出切点的作用在Around后面的字符串就是切点表达式Spring启动时会扫描所有Bean的方法把符合这个表达式的方法标记为连接点比如BookController.addBook()符合规则就被织入了计时逻辑3.2 连接点(Join Point) - 符合条件的具体方法概念切点表达式匹配到的每一个具体方法就是连接点。// 假设我们有以下类 package com.example.demo.controller; RestController public class UserController { RequestMapping(/addUser) public String addUser() { /* ... */ } // 这是连接点因为匹配规则 RequestMapping(/delUser) private String delUser() { /* ... */ } // 这不是连接点private方法无法被代理 } RestController public class OrderController { RequestMapping(/addOrder) public String addOrder() { /* ... */ } // 这是连接点 } // 切点表达式execution(* com.example.demo.controller.*.*(..)) // 匹配结果 // ✔ UserController.addUser() - 匹配public、在controller包下 // ✘ UserController.delUser() - 不匹配private修饰 // ✔ OrderController.addOrder() - 匹配 // 代码体现在TimeAspect中pjp.getSignature()能获取到的方法名就是当前正在执行的连接点如何从代码中验证连接点运行后看日志String addBook()、String queryBookById()被打印出来这些就是具体的连接点每一个都是切点表达式筛选出来的候选人3.3 通知(Advice) - 增强的具体逻辑概念真正要执行的增强代码Spring提供了5种通知类型。// 完整展示5种通知的代码 import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; // 方法签名接口 import org.aspectj.lang.annotation.*; // 导入所有AOP注解 import org.springframework.stereotype.Component; Slf4j Component Aspect public class AllAdviceDemo { // 前置通知 // 在目标方法**之前**执行无法阻止方法执行除非抛异常 Before(execution(* com.example.demo.controller.*.*(..))) public void doBefore(JoinPoint jp) { // JoinPoint连接点信息比ProceedingJoinPoint少一个proceed() log.info(【前置通知】方法准备执行: {}, jp.getSignature().getName()); // 可以在这里做参数校验、权限检查、日志记录 } // 后置通知 // 在目标方法**之后**执行无论方法是否成功或抛异常**都会执行**类似finally After(execution(* com.example.demo.controller.*.*(..))) public void doAfter(JoinPoint jp) { log.info(【后置通知】方法执行完毕: {}, jp.getSignature().getName()); // 可以在这里做资源释放、清理工作 } // 返回后通知 // 在目标方法**成功返回后**执行**方法抛出异常时不执行** AfterReturning(value execution(* com.example.demo.controller.*.*(..)), returning result) // returning指定接收返回值的参数名 public void doAfterReturning(JoinPoint jp, Object result) { log.info(【返回后通知】方法成功返回返回值: {}, result); // 可以在这里做结果缓存、成功日志 } // 异常后通知 // 在目标方法**抛出异常后**执行**方法成功返回时不执行** AfterThrowing(value execution(* com.example.demo.controller.*.*(..)), throwing e) // throwing指定接收异常的参数名 public void doAfterThrowing(JoinPoint jp, Exception e) { log.info(【异常后通知】方法抛出异常: {}, e.getMessage()); // 可以在这里做异常记录、发送告警 } // 环绕通知 // **最强大**的通知可以完全控制目标方法的执行 // 在目标方法**前后**都执行可以阻止方法执行、修改参数、修改返回值 Around(execution(* com.example.demo.controller.*.*(..))) public Object doAround(ProceedingJoinPoint pjp) throws Throwable { log.info(【环绕通知-前】方法准备执行: {}, pjp.getSignature().getName()); // 可以在这里修改参数 Object[] args pjp.getArgs(); // args[0] modified; // 修改第一个参数 Object result null; try { result pjp.proceed(args); // 执行目标方法可以传入修改后的参数 log.info(【环绕通知-后】方法成功执行准备返回); // 可以在这里修改返回值 // result modified result; } catch (Throwable e) { log.info(【环绕通知-异常】方法执行出错); throw e; // 必须抛出异常否则异常被吞掉 } finally { log.info(【环绕通知-最终】无论如何都会执行); } return result; // 必须返回结果 } }如何从代码中理解5种通知的区别// 测试Controller RestController public class TestController { RequestMapping(/test1) public String test1() { return success; // 正常返回 } RequestMapping(/test2) public String test2() { int i 1 / 0; // 抛出ArithmeticException return success; } } // 访问/test1的输出顺序 // 【前置通知】方法准备执行: test1 // 【环绕通知-前】方法准备执行: test1 // 【环绕通知-后】方法成功执行准备返回 // 【环绕通知-最终】无论如何都会执行 // 【返回后通知】方法成功返回返回值: success // 【后置通知】方法执行完毕: test1 // 访问/test2的输出顺序 // 【前置通知】方法准备执行: test2 // 【环绕通知-前】方法准备执行: test2 // 【环绕通知-异常】方法执行出错 // 【环绕通知-最终】无论如何都会执行 // 【异常后通知】方法抛出异常: / by zero // 【后置通知】方法执行完毕: test2 // 注意【返回后通知】没有执行因为方法抛异常了 // 结论体现 // 1. AfterReturning只在成功时执行 → 代码中test2抛异常它没有打印 // 2. AfterThrowing只在异常时执行 → 代码中test2抛异常它打印了 // 3. After无论成败都执行 → 两个测试都打印了 // 4. Around最强大 → 代码中可以控制是否调用pjp.proceed()3.4 切面(Aspect) - 切点 通知的集合概念切面 切点在哪里增强 通知增强什么是一个完整的增强模块。// 一个完整的切面类 import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; Slf4j Component Aspect // 标识这是一个切面类 public class LogAspect { // 定义切点 // 把公共的切点表达式提取出来避免重复写 // 方法名pt()就是切点的ID其他通知可以通过pt()引用 Pointcut(execution(* com.example.demo.service.*.*(..))) private void pt() {} // 方法体为空因为Pointcut只需要表达式 // 定义通知 // 引用切点pt() Before(pt()) public void beforeLog() { log.info(记录日志 - 方法开始); } After(pt()) public void afterLog() { log.info(记录日志 - 方法结束); } // 这个类整体就是一个切面 // 它包含了 // 1. 切点定义pt() // 2. 通知定义beforeLog()、afterLog() // 3. 类注解Aspect }如何从代码中看出切面 切点 通知Pointcut定义了规则在哪里Before、After定义了逻辑做什么它们共同存在于一个Aspect类中形成了一个完整的增强模块4. 通知执行顺序代码验证结论4.1 同一切面内的执行顺序// 结论代码验证 Aspect Component public class OrderDemo { Around(pt()) public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println(Around-前); Object result pjp.proceed(); System.out.println(Around-后); return result; } Before(pt()) public void before() { System.out.println(Before); } After(pt()) public void after() { System.out.println(After); } AfterReturning(pt()) public void afterReturning() { System.out.println(AfterReturning); } } // 调用目标方法后输出 // Around-前 // Before // 目标方法执行 // Around-后 // After // AfterReturning // 代码如何体现结论 // 1. Around先开始 → 代码中around()先打印Around-前 // 2. Before在目标方法前 → 代码中before()在pjp.proceed()前打印 // 3. Around后可以拦截返回值 → 代码中around()可以修改result变量 // 4. After在AfterReturning前 → 代码中after()打印在afterReturning()前面4.2 多个切面的执行顺序Order注解// 切面A Slf4j Aspect Component Order(1) // 数字越小优先级越高最先执行 public class AspectA { Before(execution(* com.example.demo.controller.*.*(..))) public void beforeA() { log.info(【AspectA-前置】order1); } After(execution(* com.example.demo.controller.*.*(..))) public void afterA() { log.info(【AspectA-后置】order1); } } // 切面B Slf4j Aspect Component Order(2) // 比AspectA晚执行 public class AspectB { Before(execution(* com.example.demo.controller.*.*(..))) public void beforeB() { log.info(【AspectB-前置】order2); } After(execution(* com.example.demo.controller.*.*(..))) public void afterB() { log.info(【AspectB-后置】order2); } } // 调用目标方法后输出 // 【AspectA-前置】order1 // 【AspectB-前置】order2 // 目标方法执行 // 【AspectB-后置】order2 // 【AspectA-后置】order1 // 代码如何体现结论 // 1. Order(1)先执行 → 代码中AspectA的beforeA()先打印 // 2. Order大的后执行 → 代码中AspectB的beforeB()后打印 // 3. 后置通知顺序相反 → 代码中afterB()在afterA()前打印先进后出像栈结构 // 4. 为什么因为After是在finally中执行的执行顺序与调用顺序相反5. 切点表达式详解精确匹配5.1 execution表达式最常用// 语法execution(修饰符 返回值 包名.类名.方法名(参数) 异常) // 示例1精确匹配 Pointcut(execution(public String com.example.demo.controller.BookController.addBook(BookInfo))) private void exactMatch() {} // 只匹配这一个方法 // 示例2宽泛匹配 Pointcut(execution(* com.example.demo.controller.*.*(..))) private void broadMatch() {} // 匹配controller包下所有类的所有方法 // 示例3匹配特定后缀的方法 Pointcut(execution(* com.example.demo.service.*Service.*(..))) private void serviceMatch() {} // 匹配所有以Service结尾的类的方法 // 示例4匹配特定前缀的方法 Pointcut(execution(* com.example.demo.service.*.save*(..))) private void saveMatch() {} // 匹配所有以save开头的方法 // 示例5匹配无参方法 Pointcut(execution(* com.example.demo.controller.*.*())) private void noArgsMatch() {} // 注意(..)变成了() // 示例6匹配第一个参数是String的方法 Pointcut(execution(* com.example.demo.service.*.save(String, ..))) private void firstArgMatch() {} // 第一个参数必须是String后面可以有任意参数 // 代码如何体现匹配粒度 // 1. 精确匹配 → 代码中只写了addBook一个方法其他方法不受影响 // 2. 宽泛匹配 → 代码中*通配符多个类被匹配 // 3. 后缀匹配 → 代码中*Service所有Service类都被匹配 // 4. 前缀匹配 → 代码中save*所有save方法都被匹配 // 5. 参数匹配 → 代码中String, ..精确控制参数类型5.2 annotation表达式注解匹配// 场景有些方法需要增强有些不需要用注解标记 // 步骤1自定义注解 import java.lang.annotation.ElementType; // 注解作用目标类型类、方法、字段等 import java.lang.annotation.Retention; // 注解保留策略源码、字节码、运行时 import java.lang.annotation.RetentionPolicy; // 保留策略的枚举 import java.lang.annotation.Target; // 指定注解的作用目标 // Target这个注解只能用在方法上 Target(ElementType.METHOD) // Retention这个注解在运行时有效Spring AOP通过反射读取它 Retention(RetentionPolicy.RUNTIME) public interface LogExecutionTime { // 可以添加配置属性比如String value() default ; // 这里简化不写任何属性 } // 代码如何体现注解的作用 // 1. Target限制使用位置 → 代码中只能用在方法上不能用在类或字段上 // 2. Retention(RUNTIME) → 代码中运行时Spring能读取到这个注解 // 3. interface → 代码中定义了一个注解类型 // 步骤2切面类使用annotation import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; Aspect Component public class AnnotationAspect { // annotation匹配所有标注了LogExecutionTime的方法 // 括号里是注解的全限定类名 Around(annotation(com.example.demo.aspect.LogExecutionTime)) public Object logTime(ProceedingJoinPoint pjp) throws Throwable { long start System.currentTimeMillis(); Object result pjp.proceed(); long end System.currentTimeMillis(); System.out.println(pjp.getSignature().getName() 耗时: (end - start) ms); return result; } } // 步骤3在需要增强的方法上添加注解 import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; RestController public class ProductController { RequestMapping(/addProduct) LogExecutionTime // 只有这个方法会被增强 public String addProduct() { return 商品添加成功; } RequestMapping(/delProduct) // 没有LogExecutionTime注解不会被增强 public String delProduct() { return 商品删除成功; } } // 代码如何体现注解的精确控制 // 1. LogExecutionTime像开关 → 代码中addProduct()有注解被增强 // 2. delProduct()无注解 → 代码中没有被打印耗时 // 3. 对比execution注解方式更精确不需要写复杂的包路径6. Spring AOP实现原理源码级理解6.1 代理模式基础先理解设计模式6.1.1 静态代理手动写代理类// 1. 定义接口Subject public interface HouseService { void rentHouse(); // 出租房子的方法 } // 2. 真实实现类RealSubject public class RealHouseService implements HouseService { Override public void rentHouse() { System.out.println(房东签合同、收房租); // 核心业务 } } // 3. 代理类Proxy- 手动编写 public class HouseServiceProxy implements HouseService { // 持有真实对象的引用组合关系 private HouseService realHouseService; // 通过构造器注入真实对象 public HouseServiceProxy(HouseService realHouseService) { this.realHouseService realHouseService; } Override public void rentHouse() { // 代理增强逻辑 System.out.println(中介带看房、谈价格); // 前置增强 // 调用真实对象的方法核心业务 realHouseService.rentHouse(); // 代理增强逻辑 System.out.println(中介收中介费、帮维修); // 后置增强 } } // 4. 使用代理 public class StaticProxyDemo { public static void main(String[] args) { // 创建真实对象房东 HouseService realHouseService new RealHouseService(); // 创建代理对象中介把真实对象传进去 HouseService proxy new HouseServiceProxy(realHouseService); // 通过代理调用客户找中介租房 proxy.rentHouse(); // 输出 // 中介带看房、谈价格 // 房东签合同、收房租 // 中介收中介费、帮维修 } } // 代码如何体现静态代理的缺点 // 1. 代码重复 → 每个方法都要写一遍代理逻辑rentHouse()、sellHouse()都要写 // 2. 不灵活 → 如果增加新方法代理类必须修改 // 3. 硬编码 → 增强逻辑写死在代理类里6.1.2 动态代理自动生成代理类JDK动态代理目标必须实现接口// 1. 接口和实现类同上 public interface HouseService { void rentHouse(); } public class RealHouseService implements HouseService { public void rentHouse() { System.out.println(房东出租房子); } } // 2. 实现InvocationHandler代理逻辑处理器 import java.lang.reflect.InvocationHandler; // JDK提供的代理处理器接口 import java.lang.reflect.Method; // 反射的Method类代表一个方法 public class HouseInvocationHandler implements InvocationHandler { // 目标对象被代理的对象 private Object target; public HouseInvocationHandler(Object target) { this.target target; } /** * 代理对象调用任何方法时都会进入这个方法 * param proxy 代理对象本身很少用 * param method 被调用的方法对象可以获取方法名、参数等 * param args 方法调用时传入的参数数组 * return 方法执行结果 * throws Throwable 可能抛出的异常 */ Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(中介开始服务); // 前置增强 // 通过反射调用目标方法 // method.invoke() 调用target对象的method方法参数是args Object result method.invoke(target, args); System.out.println(中介服务结束); // 后置增强 return result; } } // 3. 创建代理对象 import java.lang.reflect.Proxy; // JDK的代理工具类 public class JdkProxyDemo { public static void main(String[] args) { // 创建真实对象 HouseService target new RealHouseService(); // 创建代理对象 // Proxy.newProxyInstance参数 // 1. classLoader用目标类的类加载器 // 2. interfaces代理要实现哪些接口这里实现HouseService接口 // 3. invocationHandler代理逻辑处理器 HouseService proxy (HouseService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器加载代理类到JVM new Class[]{HouseService.class}, // 实现的接口数组 new HouseInvocationHandler(target) // 代理逻辑处理器 ); // 调用代理对象的方法 proxy.rentHouse(); // 会自动进入invoke()方法 // 输出 // 中介开始服务 // 房东出租房子 // 中介服务结束 } } // 代码如何体现动态代理的优势 // 1. 无需为每个类写代理类 → 代码中Proxy.newProxyInstance()自动生成代理 // 2. 通用性强 → 代码中target可以是任何实现了接口的对象 // 3. 灵活 → 代码中InvocationHandler可以复用给多个目标类CGLIB动态代理目标不需要实现接口// 1. 真实类没有实现接口 public class RealHouseService { public void rentHouse() { System.out.println(房东出租房子无接口); } } // 2. 实现MethodInterceptor方法拦截器 import org.springframework.cglib.proxy.MethodInterceptor; // CGLIB的方法拦截器接口 import org.springframework.cglib.proxy.MethodProxy; // CGLIB的方法代理比JDK的Method性能高 public class HouseMethodInterceptor implements MethodInterceptor { private Object target; // 目标对象 public HouseMethodInterceptor(Object target) { this.target target; } /** * 拦截目标方法调用 * param obj 代理对象本身 * param method 被调用的方法对象 * param args 方法参数 * param proxy CGLIB的方法代理对象用于调用父类方法 * return 方法执行结果 * throws Throwable */ Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(中介开始服务CGLIB); // 调用父类目标类的方法 // proxy.invokeSuper()调用被代理类的父类方法即目标方法 // 比JDK的method.invoke()性能高因为直接操作字节码 Object result proxy.invokeSuper(target, args); System.out.println(中介服务结束CGLIB); return result; } } // 3. 创建代理对象 import org.springframework.cglib.proxy.Enhancer; // CGLIB的增强器 public class CglibProxyDemo { public static void main(String[] args) { // 创建真实对象 RealHouseService target new RealHouseService(); // 创建代理对象 // Enhancer.create参数 // 1. 目标类的Class对象 // 2. 方法拦截器 RealHouseService proxy (RealHouseService) Enhancer.create( target.getClass(), // 目标类 new HouseMethodInterceptor(target) // 拦截器 ); // 调用代理对象 proxy.rentHouse(); // 输出 // 中介开始服务CGLIB // 房东出租房子无接口 // 中介服务结束CGLIB } } // 代码如何体现CGLIB的特点 // 1. 无需接口 → 代码中RealHouseService直接是类 // 2. 生成子类 → 代码中Enhancer.create()会生成RealHouseService的子类 // 3. 性能更高 → 代码中MethodProxy.invokeSuper()比反射快6.2 Spring AOP如何选择代理方式源码逻辑// Spring AOP代理选择规则通过配置控制 // 情况1目标类实现了接口默认使用JDK代理 Service public class UserServiceImpl implements UserService { // 实现了UserService接口 // Spring默认用JDK代理 } // 情况2目标类没实现接口只能用CGLIB Service public class ProductService { // 没有实现接口 // Spring强制用CGLIB代理 } // 情况3强制使用CGLIB配置 SpringBootApplication EnableAspectJAutoProxy(proxyTargetClass true) // 关键配置 public class MyApplication { // proxyTargetClass true强制使用CGLIB代理 // 即使实现了接口也用CGLIB } // 代码如何体现选择逻辑 // 1. 没有配置 → 代码中Spring自动判断有接口就用JDK无接口用CGLIB // 2. 配置proxyTargetClasstrue → 代码中所有类都用CGLIB代理 // 3. 为什么需要强制 → 代码中CGLIB可以代理类的方法JDK只能代理接口方法Spring AOP代理选择源码简化版// 这是Spring APO选择代理的核心逻辑伪代码 public class ProxyFactory { public AopProxy createAopProxy(Object target) { // 1. 如果配置了强制使用CGLIB if (proxyTargetClass) { return new CglibProxy(target); } // 2. 如果目标实现了接口 if (target.getClass().getInterfaces().length 0) { return new JdkProxy(target); // 使用JDK动态代理 } // 3. 否则使用CGLIB return new CglibProxy(target); } } // 代码如何体现选择逻辑 // 1. 优先检查配置 → 代码中if(proxyTargetClass)优先判断 // 2. 次选接口 → 代码中else if检查getInterfaces() // 3. 最后保底 → 代码中return new CglibProxy()6.3 代理执行流程方法调用时发生了什么// 当你调用被代理的方法时实际执行流程 // 假设有 RestController public class BookController { RequestMapping(/add) public String add() { return ok; } } // 调用bookController.add() 时 // JDK代理执行流程 // 1. 你调用的是**代理对象**的add()方法 // 2. 自动进入JdkDynamicAopProxy.invoke()方法 public Object invoke(Object proxy, Method method, Object[] args) { // 3. 获取所有增强器通知 ListAdvisor advisors getAdvisors(); // 4. 创建MethodInvocation方法调用链 MethodInvocation invocation new ReflectiveMethodInvocation( proxy, target, method, args, targetClass, advisors ); // 5. 执行拦截器链依次执行所有通知 return invocation.proceed(); // 关键所有通知在这里执行 } // CGLIB代理执行流程 // 1. 你调用的是**代理子类**的add()方法 // 2. 自动进入CglibAopProxy.intercept()方法 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) { // 3. 获取所有增强器 ListAdvisor advisors getAdvisors(); // 4. 创建MethodInvocation MethodInvocation invocation new CglibMethodInvocation( proxy, target, method, args, targetClass, advisors, methodProxy ); // 5. 执行拦截器链 return invocation.proceed(); } // 代码如何体现执行流程 // 1. 代理对象 → 代码中proxy不是原始对象而是Proxy.newProxyInstance()创建的 // 2. 自动拦截 → 代码中invoke()/intercept()由JDK/CGLIB自动调用 // 3. 拦截器链 → 代码中invocation.proceed()会遍历所有通知并执行7. 完整项目实战整合所有知识点7.1 项目结构com.example.demo ├── annotation │ └── LogExecutionTime.java // 自定义注解 ├── aspect │ ├── LogAspect.java // 日志切面 │ ├── TimeAspect.java // 耗时切面 │ └── SecurityAspect.java // 安全切面 ├── controller │ ├── UserController.java // 用户接口 │ └── OrderController.java // 订单接口 └── DemoApplication.java // 启动类7.2 完整代码带全套注释自定义注解// annotation/LogExecutionTime.java import java.lang.annotation.ElementType; // 注解作用目标 import java.lang.annotation.Retention; // 注解保留策略 import java.lang.annotation.RetentionPolicy; // 运行时保留 import java.lang.annotation.Target; // 目标为METHOD // 这个注解用于标记需要记录日志的方法 Target(ElementType.METHOD) // 只能用在方法上 Retention(RetentionPolicy.RUNTIME) // 运行时通过反射读取 public interface LogExecutionTime { String value() default ; // 可以添加描述信息 }日志切面// aspect/LogAspect.java import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; // 连接点信息 import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; // 方法签名 import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; // 顺序注解 import org.springframework.stereotype.Component; Slf4j Component Aspect Order(1) // 优先级最高最先执行 public class LogAspect { // 定义切点controller包下所有方法 Pointcut(execution(* com.example.demo.controller.*.*(..))) public void controllerPointcut() {} // 定义切点带有LogExecutionTime注解的方法 Pointcut(annotation(com.example.demo.annotation.LogExecutionTime)) public void annotationPointcut() {} // 前置通知记录方法开始 Before(controllerPointcut()) public void logBefore(JoinPoint jp) { Signature signature jp.getSignature(); // 获取方法签名 String className jp.getTarget().getClass().getSimpleName(); // 获取类名 String methodName signature.getName(); // 获取方法名 log.info(【日志】{}.{} 开始执行, className, methodName); } // 返回后通知记录方法成功返回 AfterReturning(value controllerPointcut(), returning result) public void logAfterReturning(JoinPoint jp, Object result) { log.info(【日志】{} 返回结果: {}, jp.getSignature().getName(), result); } // 异常后通知记录方法异常 AfterThrowing(value controllerPointcut(), throwing e) public void logAfterThrowing(JoinPoint jp, Exception e) { log.error(【日志】{} 发生异常: {}, jp.getSignature().getName(), e.getMessage()); } }耗时切面// aspect/TimeAspect.java import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; Slf4j Component Aspect Order(2) // 优先级次之在LogAspect之后执行 public class TimeAspect { // 环绕通知计算耗时 Around(execution(* com.example.demo.controller.*.*(..))) public Object calculateTime(ProceedingJoinPoint pjp) throws Throwable { Signature signature pjp.getSignature(); String methodName signature.getName(); long startTime System.currentTimeMillis(); log.info(【计时】{} 开始, methodName); Object result pjp.proceed(); // 执行目标方法 long endTime System.currentTimeMillis(); long costTime endTime - startTime; log.info(【计时】{} 结束耗时: {}ms, methodName, costTime); return result; } }安全切面// aspect/SecurityAspect.java import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; Slf4j Component Aspect Order(3) // 优先级最低最后执行 public class SecurityAspect { // 前置通知权限检查 Before(annotation(com.example.demo.annotation.LogExecutionTime)) public void checkPermission(JoinPoint jp) { Signature signature jp.getSignature(); String methodName signature.getName(); // 模拟权限检查 log.info(【安全】检查 {} 的权限, methodName); // 实际应用从Session获取用户角色检查是否有权限 // if (!hasPermission()) { throw new SecurityException(无权限); } } }Controller// controller/UserController.java import com.example.demo.annotation.LogExecutionTime; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; RestController RequestMapping(/user) public class UserController { GetMapping(/add) LogExecutionTime // 添加注解会被安全切面检查 public String addUser() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } return 用户添加成功; } GetMapping(/delete) public String deleteUser() { return 用户删除成功; } } // controller/OrderController.java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; RestController RequestMapping(/order) public class OrderController { GetMapping(/create) public String createOrder() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } return 订单创建成功; } }启动类// DemoApplication.java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.EnableAspectJAutoProxy; SpringBootApplication EnableAspectJAutoProxy(proxyTargetClass true) // 启用CGLIB代理强制使用CGLIB public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context SpringApplication.run(DemoApplication.class, args); // 启动后可以用context获取Bean进行测试 } }7.3 测试结果分析结论与代码对应测试1访问 /user/add控制台输出顺序 1. 【日志】UserController.addUser 开始执行 ← LogAspect前置通知 2. 【计时】addUser 开始 ← TimeAspect环绕通知-前 3. 【安全】检查 addUser 的权限 ← SecurityAspect前置通知注解匹配 4. 【计时】addUser 结束耗时: 105ms ← TimeAspect环绕通知-后 5. 【日志】addUser 返回结果: 用户添加成功 ← LogAspect返回后通知 结论对应代码 1. Order(1)优先执行 → LogAspect的Before先打印 2. Order(2)次之 → TimeAspect的Around在Before之后执行 3. annotation精确匹配 → SecurityAspect只对LogExecutionTime生效 4. 环绕通知包裹 → TimeAspect的计时包含了SecurityAspect的权限检查时间 5. 返回后通知 → LogAspect的AfterReturning拿到返回值测试2访问 /user/delete控制台输出 1. 【日志】UserController.deleteUser 开始执行 2. 【计时】deleteUser 开始 3. 【计时】deleteUser 结束耗时: 1ms 4. 【日志】deleteUser 返回结果: 用户删除成功 结论对应代码 - 没有【安全】日志 → deleteUser()没有LogExecutionTime注解 - SecurityAspect的切点只匹配注解所以不生效测试3访问 /order/create控制台输出 1. 【日志】OrderController.createOrder 开始执行 2. 【计时】createOrder 开始 3. 【计时】createOrder 结束耗时: 202ms 4. 【日志】createOrder 返回结果: 订单创建成功 结论对应代码 - execution(* controller.*.*(..))匹配所有controller - LogAspect、TimeAspect对UserController和OrderController都生效 - SecurityAspect只对添加了LogExecutionTime的方法生效8. 常见问题与解决方案代码级避坑8.1 同一个类内方法调用AOP失效Service public class UserService { public void methodA() { System.out.println(执行methodA); methodB(); // 直接调用同类方法不会触发AOP } LogExecutionTime // 期望被增强 public void methodB() { System.out.println(执行methodB); } } // 问题代码分析 // methodA()调用methodB()时调用的是this.methodB()this是原始对象不是代理对象 // Spring AOP只对代理对象的方法调用生效 // 解决方案1注入自己获取代理对象 Service public class UserService { Autowired private UserService self; // 注入Spring管理的代理对象 public void methodA() { System.out.println(执行methodA); self.methodB(); // 通过代理对象调用AOP生效 } LogExecutionTime public void methodB() { System.out.println(执行methodB); } } // 解决方案2使用AopContext需要配置expose-proxytrue Service public class UserService { public void methodA() { System.out.println(执行methodA); ((UserService) AopContext.currentProxy()).methodB(); // 获取当前代理对象 } LogExecutionTime public void methodB() { System.out.println(执行methodB); } } // 配置类 EnableAspectJAutoProxy(exposeProxy true) // 暴露代理对象到AopContext8.2 通知中抛出异常导致原方法无法执行Around(execution(* com.example.demo.service.*.*(..))) public Object badAdvice(ProceedingJoinPoint pjp) throws Throwable { // 错误示例1忘记调用pjp.proceed() System.out.println(前置逻辑); // 没有pjp.proceed()原方法不会被执行 return null; // 调用方拿到null可能引发NPE // 错误示例2吞掉异常 try { return pjp.proceed(); } catch (Exception e) { System.out.println(出错了); // 没有throw异常被吞掉调用方以为成功了 return null; } // 正确做法 try { return pjp.proceed(); } catch (Throwable e) { System.out.println(出错了: e.getMessage()); throw e; // 必须抛出让调用方感知异常 } } // 代码如何体现问题 // 1. 忘记proceed() → 代码中目标方法根本没执行 // 2. 吞掉异常 → 代码中catch后没有throw异常链断裂 // 3. 正确做法 → 代码中catch后必须throw保持异常传播8.3 切点表达式过于宽泛影响性能// 错误示例匹配所有方法 Around(execution(* *.*(..))) // 危险会匹配所有类包括Spring内置的Bean public Object allMethods(ProceedingJoinPoint pjp) throws Throwable { // 这会拦截Spring自己的方法如Bean初始化、AOP代理创建等 // 导致启动慢、性能差、不可预知的错误 return pjp.proceed(); } // 正确示例精确匹配 Around(execution(* com.example.demo.controller.*.*(..)) !execution(* com.example.demo.controller.HealthController.*(..))) // 排除健康检查 public Object preciseMethods(ProceedingJoinPoint pjp) throws Throwable { return pjp.proceed(); } // 代码如何体现性能问题 // 1. 宽泛表达式 → 代码中*.*会匹配到Spring内部Bean的方法 // 2. 精确表达式 → 代码中明确指定包路径并用!排除不需要的类 // 3. 启动时间 → 宽泛表达式会让Spring启动时处理大量不必要的代理9. 总结知识地图9.1 核心概念关系图┌─────────────────────────────────────────────────────────────┐ │ 切面 (Aspect) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 切点 (Pointcut) │ │ │ │ execution(* com.example.controller.*(..)) │ │ │ │ annotation(com.example.annotation.Log) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ | │ │ | 匹配 │ │ v │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 连接点 (Join Point) │ │ │ │ UserController.addUser() │ │ │ │ OrderController.createOrder() │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ | │ │ | 应用 │ │ v │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 通知 (Advice) │ │ │ │ Before: 前置逻辑 │ │ │ │ Around: 环绕逻辑 │ │ │ │ After: 后置逻辑 │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘9.2 代理方式选择决策树目标类是否实现了接口 ├─ 是 → 是否配置proxyTargetClasstrue │ ├─ 是 → 使用CGLIB代理 │ └─ 否 → 使用JDK动态代理 └─ 否 → 使用CGLIB代理强制9.3 最佳实践清单场景推荐做法代码示例精确匹配使用annotation或精确包路径Around(annotation(Log))多个切面用Order控制顺序Order(1)代理选择默认即可有特殊需求再配置CGLIBEnableAspectJAutoProxy(proxyTargetClass true)内部调用注入自己或使用AopContextself.methodB()异常处理环绕通知中必须throw异常catch(Throwable e) { throw e; }10. 最后的叮嘱给新手的建议先跑起来不要纠结细节先把第一个Around例子跑通再理解概念对照代码和日志理解切点、连接点、通知的关系后玩复杂熟练后再尝试多个切面、annotation、Order等高级玩法看日志日志是理解AOP执行顺序的最好工具调试在通知方法里打断点看调用栈能清晰看到代理链路记住AOP就像给代码穿外套不穿外套的人原始类完全不知道外套的存在但外套确实提供了额外的功能日志、计时、安全。这就是无侵入式编程的魅力恭喜你完成了Spring AOP的深度学习 现在你应该能写出带详细注释的切面代码解释每个注解的作用通过日志验证执行顺序选择合适的代理方式避开了常见的坑如果还有疑问随时回来翻代码和注释它们是最诚实的老师