Spring AOP 完整讲解

📅 2026/7/15 23:27:16
Spring AOP 完整讲解
一、AOP 基础概念1. 什么是 AOPAOP面向切面编程Aspect Oriented Programming核心思想横向抽取通用公共逻辑不侵入业务代码实现解耦。 传统 OOP 是纵向继承AOP 是横向切面统一拦截增强。2. 解决什么问题业务代码中大量重复逻辑日志、权限校验、事务、接口限流、参数校验、性能监控、异常统一处理。 如果写在每个接口代码冗余、难维护、污染业务 AOP 统一抽成切面运行时动态切入目标方法无侵入。3. AOP 核心术语JoinPoint 连接点程序中可以被切面拦截的点Spring 中仅支持方法执行作为连接点类、字段、构造器不支持。Pointcut 切入点匹配规则筛选哪些连接点需要被拦截表达式匹配方法。Advice 通知切面要执行的增强逻辑分 5 种时机Before前置通知方法执行前AfterReturning正常返回后无异常AfterThrowing抛出异常后After最终通知无论是否异常都执行类似 finallyAround环绕通知包裹目标方法可控制执行、返回值、异常Aspect 切面切面类 切入点 通知存放增强逻辑。Target 目标对象被切面代理的原始业务对象。Proxy 代理对象Spring 运行时生成的代理类调用方法时先走切面逻辑。Weaving 织入将切面逻辑植入目标对象的过程Spring AOP 是运行时织入。二、Spring AOP 底层两种代理实现Spring 会自动选择代理方式无需手动指定1. JDK 动态代理条件目标类实现了至少一个接口原理基于接口生成代理类只能拦截接口定义的方法限制无法代理类中未在接口声明的方法2. CGLIB 字节码代理条件目标类没有实现任何接口原理继承目标类生成子类作为代理重写所有非 final 方法SpringBoot 2.x 默认使用 CGLIB 代理切换代理方式配置application.ymlyamlspring: aop: proxy-target-class: true # 强制全部使用CGLIB三、注解式 AOP步骤 1引入依赖xml!-- Spring AOP 核心依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency步骤 2开启 AOP启动类添加注解java运行SpringBootApplication EnableAspectJAutoProxy // 开启自动代理支持AOP public class App {}步骤 3完整切面示javaimport org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Date; Aspect // 标记为切面类 Component // 交给Spring管理 public class LogAspect { // 1. 定义切入点匹配controller包下所有类所有方法 Pointcut(execution(* com.demo.controller.*.*(..))) public void pointCut() {} // 前置通知 Before(pointCut()) public void before(JoinPoint joinPoint) { System.out.println(方法执行前 new Date()); // 获取方法信息 String methodName joinPoint.getSignature().getName(); Object[] args joinPoint.getArgs(); // 获取入参 } // 正常返回通知可接收返回值 AfterReturning(value pointCut(), returning result) public void afterReturn(Object result) { System.out.println(方法正常返回返回值 result); } // 异常通知捕获异常 AfterThrowing(value pointCut(), throwing ex) public void afterThrow(Exception ex) { System.out.println(方法抛出异常 ex.getMessage()); } // 最终通知一定执行 After(pointCut()) public void after() { System.out.println(方法执行完毕finally); } // 环绕通知功能最强可控制目标方法是否执行 Around(pointCut()) public Object around(ProceedingJoinPoint pjp) throws Throwable { long start System.currentTimeMillis(); // 执行目标方法 Object res pjp.proceed(); long cost System.currentTimeMillis() - start; System.out.println(方法耗时 cost ms); return res; } }通知执行顺序正常无异常Around前置 → Before → 目标方法 → AfterReturning → After → Around后置出现异常Around前置 → Before → 目标方法抛异常 → AfterThrowing → After四、Pointcut 切入点表达式详解1. execution 执行表达式语法execution(返回值 包.类.方法(参数))通配符*任意返回值、任意类、任意方法、单个参数..任意多层包、任意多个参数示例java// 匹配com.demo.service下所有类所有方法 execution(* com.demo.service.*.*(..)) // 匹配service包及所有子包 execution(* com.demo.service..*.*(..)) // 仅匹配返回值void的方法 execution(void com.demo.controller.*.*(..)) // 匹配带一个String参数的方法 execution(* com.demo.*.*(String))2. annotation 注解匹配自定义注解javaTarget(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface LogRecord {}切面切入点只拦截加了LogRecord注解的方法javaPointcut(annotation(com.demo.annotation.LogRecord))3. within 按类匹配java// 匹配controller包下所有类 within(com.demo.controller.*)4. args 按参数类型匹配java// 拦截第一个参数为Long的方法 args(Long,..)五、JoinPoint / ProceedingJoinPoint 常用 API仅环绕通知使用ProceedingJoinPoint其余通知用JoinPointjava// 获取方法签名 Signature signature joinPoint.getSignature(); MethodSignature methodSig (MethodSignature) signature; Method method methodSig.getMethod(); // 获取方法入参 Object[] args joinPoint.getArgs(); // 获取目标对象 Object target joinPoint.getTarget(); // 获取代理对象 Object proxy joinPoint.getThis(); // 获取方法名 String methodName joinPoint.getSignature().getName(); // 环绕专属执行目标方法 Object result pjp.proceed(); // 带参数执行修改入参 Object result pjp.proceed(new Object[]{newParam});六、经典实战场景场景 1自定义注解实现接口日志记录自定义LogRecord注解注解上存模块、操作类型AOP 拦截该注解获取注解属性、请求参数、返回值、耗时存入数据库场景 2全局接口权限校验AOP 拦截 Controller 方法从 Token 获取用户判断是否拥有接口权限无权限直接抛出异常场景 3统一接口性能监控环绕通知记录每个接口执行耗时超时接口打印告警日志场景 4统一异常处理补充AOP 捕获方法异常记录错误堆栈配合 RestControllerAdvice 全局异常返回统一格式场景 5分布式事务、本地事务管理Transactional 底层就是 AOPSpring 事务本质AOP 环绕通知方法前开启事务正常提交、异常回滚七、AOP 执行顺序与多切面排序1. 多个切面执行顺序使用Order(int)指定优先级数值越小优先级越高进入方法Order 小的切面先执行退出方法Order 小的切面后执行javaAspect Component Order(1) // 优先级高于Order(2) public class LogAspect {}2. 同一切面内通知顺序Around → Before → 方法执行 → AfterReturning/AfterThrowing → After八、常见坑与面试重点1. 内部调用 AOP 失效问题同一个类中A方法()调用本类B方法()B 上的切面不执行。 原因内部调用使用原始this对象调用不是代理对象不会走 AOP 拦截。 解决方案注入自身代理对象Autowired private 当前类 self;用self.B()调用拆分方法到不同类使用 Spring 上下文获取代理2. final/static 方法无法被 AOP 拦截CGLIB 通过继承重写方法final 不能重写static 属于类不属于实例无法代理。3. 私有方法不能拦截Spring AOP 仅拦截 public 方法。4. Transactional 失效场景方法 private/static/final同类内部调用异常被 try-catch 捕获未抛出传播行为配置错误5. JDK 代理只能拦截接口方法目标类无接口自动切换 CGLIBSpringBoot 默认 CGLIB。九、AOP 与 AspectJ 区别Spring AOP运行时织入动态代理实现仅支持方法连接点轻量无需额外编译开发主流Spring 内置原生 AspectJ编译期 / 加载期织入修改字节码支持方法、构造器、字段等全部连接点需要 ajc 编译器较重一般项目不用十、完整开发流程总结引入 aop starter启动类加 EnableAspectJAutoProxy编写切面类Aspect Component定义 Pointcut 切入点表达式编写 5 种通知逻辑通过 JoinPoint 获取方法信息Order 控制多切面顺序避开内部调用、private、final 等失效场景