Aop切面

Aop切面 1注解怎么被 Spring 用上的你是否知道注解本身只是“标记”本身没有任何功能2它会扫所有代码吗你是否知道 Spring 只扫自己的 Beanclasspath 扫描 BeanPostProcessor3别的注解加载方式一样吗你是否知道不同注解由不同的处理器处理Transactional、Async 都走 AOPAutowired 走注入不是一套机制4扫描之后它做了什么工作核心你是否知道是生成了代理对象5代码不会跳着执行怎么把两部分代码跑完的逻辑推理题——逼你自己推导出“肯定有个东西在中间调用了两边”6为什么有 before / after逼你想通切面代码和业务代码在同一个调用链里所以才有顺序概念1.总体介绍代理模式是一种结构型设计模式不直接访问目标对象而是通过一个“代理对象”来间接访问代理在访问前后可以附加额外的逻辑。Spring AOP 的本质就是在运行时用 JDK 动态代理或 CGLIB 给目标 Bean 生成一个代理对象容器里放的是代理对象你调用方法时实际走的是代理代理帮你插入切面逻辑。你写的: userService.save() 实际执行: 代理.save() → 前置通知 → 目标save() → 后置通知 → 返回为什么 AOP 要使用代理模式核心你问“其实就是为了插入一段代码为什么要引入代理模式”这是很多初学者的困惑。答案为了无侵入和开闭原则。如果不用代理直接“插入代码”只有两种粗暴方式硬编码在业务方法里手动写日志、事务代码侵入性强改业务代码要改切面代码。修改字节码直接用 ASM 等工具修改.class文件复杂度高难以管理。代理模式的价值在于它创造了一个“中间层”。这个中间层像一个“包装盒”把原始对象包起来。调用者拿到包装盒以为用的是原对象但实际走的是包装盒的流程。这样原始业务类核心逻辑完全不知道有切面存在无侵入。切面逻辑日志、事务完全抽离可以随时增减符合开闭原则。这本质上是“控制反转”思想的延伸你不直接调目标方法而是把控制权交给代理由代理决定何时调、怎么调。核心注解1.声明切片类Aspect // 声明我是切面类Component // ⚠️ 必须搭配否则 Spring 根本不管理它 Bean2.定义切点Pointcut(execution(* com.demo.service.*.*(..)))public void myPointcut() {} // 方法体永远为空纯粹是切点的变量名3.注解通知注解 执行时机 执行位置Before 方法执行前 无法访问返回值After 方法执行后finally 语义无论成败都执行 —AfterReturning 方法正常返回后 可拿到返回值AfterThrowing 方法抛异常后 可拿到异常对象Around 环绕包裹整个方法最强可控制是否执行 可修改参数/返回值4.自定义注解Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)public interface MyLog { }个人理解创建一个自定义注解一个切面类Aspect,一个目标类都有bean标识启动项目后扫描录入切面类和目标类后查看切面类发现其中的annotation了解到有一自定义注解倒回去查看bean中有哪个类使用了这一自定义注解发现我的目标类然后创建代理对象代理执行例如前后加日志中间让源对象执行原始逻辑① Spring 启动 → 扫描所有 Bean包括 Aspect 标记的切面类② Spring 发现切面类中 Around(annotation(LogAnnotation)) 的切点表达式③ Spring 检查容器里所有 Bean哪些方法上有 LogAnnotation④ 匹配到 OrderService.createOrder()⑤ 在创建 OrderService 这个 Bean 的最后一步BeanPostProcessor用动态代理把原始对象包装成代理对象⑥ 容器里实际存放、注入给别人的是这个代理对象日志场景完整示例自定义注解Target(ElementType.METHOD)//方法类型 Retention(RetentionPolicy.RUNTIME)//保留到运行时 public interface LogExecution { String value() default ; }切面类Aspect Component public class LogAspect { private static final Logger log LoggerFactory.getLogger(LogAspect.class); Around(annotation(logAnnotation)) public Object around(ProceedingJoinPoint joinPoint, LogExecution logAnnotation) throws Throwable { // 前记录开始时间 long start System.currentTimeMillis(); String methodName joinPoint.getSignature().getName(); log.info(开始执行方法{}说明{}, methodName, logAnnotation.value()); try { // 执行原方法 Object result joinPoint.proceed(); // 后记录结束时间和结果 long end System.currentTimeMillis(); log.info(方法执行完成{}耗时{}ms结果{}, methodName, end - start, result); return result; } catch (Exception e) { log.error(方法执行异常{}, methodName, e); throw e; } } }业务输出Service public class UserService { LogExecution(查询用户信息) public User getUserById(Long id) { // 业务逻辑 return userDao.findById(id); } }连接流程1.自定义注解类Target(ElementType.METHOD) // 只能标在方法上 Retention(RetentionPolicy.RUNTIME) // 运行时保留 public interface RepeatExecuteLimit { String name(); String[] keys(); int durationTime() default 5; String message() default 请勿重复提交; }切面类Aspect // 标记这是一个切面类 Component // 交给 Spring 容器管理 public class RepeatExecuteLimitAspect { Around(annotation(repeatLimit)) // 绑定注解 public Object around(ProceedingJoinPoint joinPoint, RepeatExecuteLimit repeatLimit) throws Throwable { // 前置逻辑 System.out.println(【切面】开始检查重复提交); // 执行原方法 Object result joinPoint.proceed(); // 后置逻辑 System.out.println(【切面】释放锁); return result; } }目标类Service // 交给 Spring 容器管理 public class OrderService { RepeatExecuteLimit(name submitOrder, keys {#userId}) public String submitOrder(Long userId, String orderId) { System.out.println(【业务】执行下单逻辑); return 订单提交成功; } }Spring AOP 自定义注解防重复提交的完整执行流程第一阶段项目启动时Spring 容器初始化第 1 步Spring Boot 启动开始扫描所有类。第 2 步扫描到Component注解的类发现RepeatExecuteLimitAspect切面类和OrderService目标类将它们都注册成 Spring 容器中的 Bean。第 3 步Spring 检测到RepeatExecuteLimitAspect类上有Aspect注解把它标记为切面类并解析类内部的所有通知方法。第 4 步解析到Around(annotation(repeatLimit))这个环绕通知Spring 理解到这个切面要拦截所有标注了RepeatExecuteLimit注解的方法并且会把注解对象作为参数传给切面方法。第 5 步Spring 开始检查容器中所有已经注册的 Bean看哪些 Bean 的方法上标注了RepeatExecuteLimit注解。发现OrderService的submitOrder方法上有这个注解判定匹配成功。第 6 步Spring 决定为OrderService这个 Bean 生成代理对象。根据OrderService是否实现了接口选择使用 JDK 动态代理或 CGLIB 代理。第 7 步代理对象创建完成它内部持有目标对象原始的OrderService和切面逻辑RepeatExecuteLimitAspect的around方法。第 8 步Spring 把容器中原本存放的原始OrderService对象替换成代理对象。从此容器里放的是代理对象原始对象被隐藏起来。第二阶段运行时用户发起请求第 1 步用户通过 Controller 调用orderService.submitOrder(123L, ORD001)。第 2 步Spring 从容器中取出orderService但实际上取出的是代理对象不是原始对象。第 3 步代理对象的invoke方法被触发它开始查找所有匹配当前方法的切面通知。第 4 步找到RepeatExecuteLimitAspect中的around方法准备执行环绕通知。第 5 步Spring 将目标方法上的RepeatExecuteLimit注解对象解析出来转换成RepeatExecuteLimit类型的实例连同ProceedingJoinPoint一起作为参数传入切面的around方法。第 6 步进入around方法执行前置切面逻辑读取注解中的name、keys、durationTime等属性根据这些信息生成 Redis 的锁 Key然后调用 Redis 的SETNX命令尝试加锁。第 7 步如果加锁失败说明已经有相同请求在执行直接抛出异常返回请勿重复提交的提示给用户整个过程结束原始业务方法根本不会执行。第 8 步如果加锁成功继续执行调用joinPoint.proceed()方法。第 9 步proceed()将控制权转交给原始的目标对象真正执行OrderService中的submitOrder业务方法执行下单逻辑返回结果。第 10 步业务方法执行完毕返回值回到切面的around方法中。第 11 步执行后置切面逻辑进入finally代码块删除 Redis 中的锁释放资源。第 12 步around方法将业务方法的结果返回给代理对象。第 13 步代理对象将结果返回给调用方Controller用户收到响应。核心要点总结三个类连接的关键自定义注解类定义标记规则切面类通过Around(annotation(参数名))建立匹配规则并接收注解实例目标类通过标注注解来表明哪些方法需要被增强。Spring 的连接机制容器启动时扫描所有 Bean 和切面匹配成功后为目标 Bean 生成代理对象并替换容器中的原对象。代理执行过程调用时先走代理代理执行切面的前置逻辑然后通过proceed()放行到原始方法最后执行切面的后置逻辑返回结果。代理和原始对象的关系容器里放的是代理对象代理对象内部持有原始对象的引用原始对象从未直接暴露给调用方。