Spring AOP 原理深度解析:从动态代理到切面织入(最新!Spring6与Spring5的差异)
摘要 :为什么同类方法调用
@Transactional会失效?为什么 AOP 代理不了static方法?JDK 动态代理和 CGLIB 到底有什么区别?拦截器链又是怎样一步步把通知串起来的?这篇文章从源码级别逐一解答,附带可直接运行的单元测试。
温馨提示 :最新Spring6和Spring5这块AOP又有什么不同?SpringBoot3.x的默认代理策略是什么?文中皆有探讨,值得关注!
标签:Spring、AOP、动态代理、JDK代理、Cglib、切面、环绕通知、责任链
-
一、从一个实际问题说起
-
二、AOP 是什么?
-
2.1 概念定义
-
2.2 为什么需要 AOP
-
三、AOP 核心术语
-
3.1 术语图解
-
3.2 术语详解
-
四、五种通知类型
-
4.1 通知类型执行顺序
-
Spring 5(Spring Boot 2.x)的执行顺序
-
Spring 6(Spring Boot 3.x)的执行顺序
-
4.2 @Before 前置通知
-
4.3 @AfterReturning 返回通知
-
4.4 @AfterThrowing 异常通知
-
4.5 @After 最终通知
-
4.6 @Around 环绕通知(最强大)
-
4.7 通知类型对比表
-
五、切点表达式详解
-
5.1 execution 表达式(最常用)
-
5.2 within 表达式
-
5.3 @annotation 表达式
-
5.4 组合表达式
-
六、AOP 底层原理(核心章节)
-
6.1 两种代理方式(版本也有差异!)
-
6.2 JDK 动态代理:完整源码分析
-
Proxy.newProxyInstance 内部做了什么
-
InvocationHandler.invoke 如何分发到目标方法
-
6.3 CGLIB 代理:完整源码分析
-
Enhancer.create 如何生成子类
-
MethodInterceptor.intercept 的拦截流程
-
invokeSuper vs invoke 的区别
-
final 方法为什么不能被 CGLIB 代理
-
6.4 拦截器链执行原理(责任链模式)
-
CglibMethodInvocation.proceed 的责任链模式
-
多个通知如何组成拦截器链
-
6.5 AnnotationAwareAspectJAutoProxyCreator 工作原理
-
从 @EnableAspectJAutoProxy 到注册
-
postProcessAfterInitialization 中如何判断是否需要创建代理
-
6.6 @EnableAspectJAutoProxy(exposeProxy=true) 底层实现
-
AopContext 的 ThreadLocal 机制
-
currentProxy() 如何获取当前代理
-
setCurrentProxy(oldProxy) 设计哲学
-
类比理解:函数调用栈
-
总结对比
-
为什么默认关闭(性能考虑)
-
6.7 AspectJ 编译时织入 vs Spring AOP 运行时代理
-
AspectJ 的三种织入方式
-
对比 Spring AOP
-
6.8 springboot3.x其AOP默认代理
-
七、AOP 的应用场景
-
7.1 日志记录(最常用)
-
7.2 缓存处理
-
7.3 权限校验
-
八、AOP 失效的常见陷阱
-
陷阱 1:同类方法调用(最高频)
-
陷阱 2:非 public 方法
-
陷阱 3:静态方法
-
陷阱 4:final 方法
-
陷阱 5:自调用(直接 new 对象)
-
九、切面执行顺序
-
9.1 @Order 注解
-
9.2 Ordered 接口
-
9.3 洋葱模型
-
十、常见误区
-
误区 1:认为 @Around 比 @Before 慢
-
误区 2:认为 AOP 可以代理 static 方法
-
误区 3:认为同类调用 @Transactional 生效
-
误区 4:认为切面执行顺序只由 @Order 决定
-
误区 5:认为 JDK 动态代理一定比 CGLIB 慢
-
十一、最佳实践
-
- 优先使用 @Around 处理复杂逻辑
-
- 避免在切面中使用 @Autowired 的懒加载问题
-
- 同类调用优先拆分 Service
-
- 使用自定义注解代替复杂切点表达式
-
- 使用 exposeProxy 解决同类调用的注意事项
-
十二、选型决策树
-
十三、单元测试(结果)
-
测试 1:AopProxyTest
-
测试 2:AopFailureTest
-
测试 3:AspectOrderTest
-
十四、Spring 5 vs Spring 6 核心差异总结
-
14.1 通知执行顺序变化
-
14.2 默认代理策略变化
-
14.3 迁移指南
-
需要调整的地方
-
不需要调整的地方
-
14.4 版本选择建议
-
十五、总结
-
核心要点速查
-
最佳实践清单
-
十六、面试自测
-
基础知识
-
Spring 5 vs Spring 6 差异专项
-
十七、参考资料
-
Spring 官方文档
-
Spring 源码
-
JDK & CGLIB 源码
-
书籍
-
迁移指南
一、从一个实际问题说起
小 A 在开发一个电商系统时,遇到了这样的困扰:
@Service public class OrderService { public void createOrder () { // 1. 日志记录 log.info( "创建订单开始" ); // 2. 权限检查 if (!userHasPermission()) { throw new SecurityException ( "无权限" ); } // 3. 事务控制 try { orderRepository.save(); } catch (Exception e) { throw e; } // 4. 日志记录 log.info( "创建订单结束" ); } public void cancelOrder () { // 重复的日志、权限、事务代码... } }
小 A 发现:
-
- 代码重复:每个方法都要写日志、权限、事务
-
- 难以维护:修改日志格式要改很多地方
-
- 业务不纯粹:业务逻辑里掺杂了太多非业务代码
这就是 AOP 要解决的问题。
后来,小 A 给 Service 加了 @Transactional,却发现 createUser() 内部调用 saveUser() 时,事务竟然不生效。查了半天才意识到:Spring AOP 基于代理,同类内部调用走的是 this,不经过代理对象。this和代理对象是两回事,this就是我们抛开spring框架,抛开代理对象,一早学习的java基础面向对象时的this,指代当前对象。
如果不知道 AOP 的底层原理,类似的"坑"会反复出现。
二、AOP 是什么?
2.1 概念定义
AOP(Aspect-Oriented Programming):面向切面编程,将横切关注点(日志、事务、权限、缓存等)从业务逻辑中分离出来,以声明式(对比编程式,直接使用注解的方式标注即可获得横切的功能,或者切点表达式)的方式织入到目标对象中。
说人话版本 :
想象一个公司的运作:
核心业务 :产品开发、销售、客服(纵切)
横切关注点 :财务报销、人事考勤、行政管理(横切)
AOP 就是把"财务、人事、行政"这些通用功能抽取出来,统一管理,而不是在每个部门内部都写一遍。
2.2 为什么需要 AOP
没有 AOP 之前,横切关注点的代码散布在各个业务方法中,导致:
- 代码重复:每个方法都写一遍日志/权限/事务
- 耦合严重:业务逻辑和非业务逻辑混在一起
- 难以修改:修改日志格式要改所有方法
AOP 的解决方案:
- 将横切关注点封装为切面(Aspect)
- 通过切点表达式声明哪些方法需要被拦截(注解是其中一种方式)
- 框架在运行时自动将切面逻辑织入目标方法(就是你看不见的地方框架会给你的添加功能,增强目标方法)
类比:没有 AOP 时,每个房间都要自己装水电。有了 AOP 后,水电管线统一铺设,房间只要声明"我需要水电"就行了。
三、AOP 核心术语
3.1 术语图解
3.2 术语详解
| 术语 | 英文 | 含义 | 示例 |
|---|---|---|---|
| 横切关注点 | Cross-cutting Concern | 横跨多个类的通用功能 | 日志、事务、权限、限流、统计、分布式锁 |
| 切面 | Aspect | 横切关注点的模块化 | @Aspect 注解的类,一个形象的表达,聚合了其他横切要素 |
| 连接点 | JoinPoint | 可以插入切面的点 | 方法执行、异常抛出,就是某类里面的某个方法执行。被封装了的方法 |
| 切点 | Pointcut | 匹配连接点的条件 | execution(service..*(..)) ,符合条件的方法集合 |
| 通知 | Advice | 在切点执行的动作 | 前置、后置、环绕,给目标方法的增强,在哪儿增强呢 |
| 目标对象 | Target | 被代理的对象 | OrderService,this |
| 代理对象 | Proxy | AOP 创建的对象 | OrderService 的代理,通过实现接口(JDK)或继承目标对象的方式(cglib) |
| 织入 | Weaving | 将切面应用到目标对象 | 创建代理的过程,因为有aop织入的需求,所以普通bean摇身一变成了代理后的bean |
四、五种通知类型
4.1 通知类型执行顺序
重要提示:Spring 5 和 Spring 6 的通知执行顺序有重大差异!
Spring 5(Spring Boot 2.x)的执行顺序
Spring 5 顺序总结 :@Around-before → @Before → 目标方法 → @Around-after → @AfterReturning → @After
Spring 6(Spring Boot 3.x)的执行顺序
Spring 6 顺序总结 :@Around-before → @Before → 目标方法 → @AfterReturning → @After → @Around-after
关键变化 :Spring 6 中
@Around-after移到了最后执行,这是为了更符合 AspectJ 的标准语义,让@Around真正"环绕"整个连接点(包括其他所有通知)。注意 :
Spring 5 :@Around 的前置部分包裹了 @Before,@Around 的后置部分在 @AfterReturning 之前执行
Spring 6 :@Around 真正环绕整个连接点,@Around-after 在所有其他通知之后执行
这是拦截器链的组织方式决定的(后续源码章节详解)
4.2 @Before 前置通知
执行时机:目标方法执行前。
@Aspect @Component public class LogAspect { @Before("execution(* com.example.service.*.*(..))") public void before (JoinPoint jp) { String methodName = jp.getSignature().getName(); System.out.println( "方法执行前:" + methodName); Object[] args = jp.getArgs(); System.out.println( "参数:" + Arrays.toString(args)); } }
特点:
- 无法阻止目标方法执行
- 无法修改返回值
使用场景:权限校验、参数验证、日志记录。
4.3 @AfterReturning 返回通知
执行时机:目标方法成功返回后。
@Aspect @Component public class LogAspect { @AfterReturning( pointcut = "execution(* com.example.service.*.*(..))", returning = "result" ) public void afterReturning (JoinPoint jp, Object result) { System.out.println( "方法返回:" + result); } }
特点:
- 可以获取返回值
- 无法修改返回值(修改也不影响调用方拿到的值)
使用场景:返回结果日志、数据脱敏、审计。
4.4 @AfterThrowing 异常通知
执行时机:目标方法抛出异常后。
@Aspect @Component public class LogAspect { @AfterThrowing( pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex" ) public void afterThrowing (JoinPoint jp, Exception ex) { System.out.println( "方法异常:" + ex.getMessage()); } }
特点:
- 可以获取异常对象
- 无法阻止异常向上传播(除非在通知内 catch 并吞掉)
使用场景:异常日志、错误告警。
4.5 @After 最终通知
执行时机:目标方法执行后(无论成功或失败)。
@Aspect @Component public class LogAspect { @After("execution(* com.example.service.*.*(..))") public void after (JoinPoint jp) { System.out.println( "方法执行完毕" ); } }
特点:
- 类似 try-catch-finally 的 finally
- 无法获取返回值或异常
使用场景:资源清理、连接关闭。
4.6 @Around 环绕通知(最强大)
执行时机:包裹目标方法执行。
@Aspect @Component public class LogAspect { @Around("execution(* com.example.service.*.*(..))") public Object around (ProceedingJoinPoint pjp) throws Throwable { // 前置逻辑 System.out.println( "方法执行前" ); try { // 执行目标方法 Object result = pjp.proceed(); // 后置逻辑 System.out.println( "方法执行后" ); return result; } catch (Throwable e) { // 异常处理 System.out.println( "方法异常" ); throw e; } } }
特点:
- 可以控制是否执行目标方法(不调用
proceed()即可阻止) - 可以修改返回值
- 可以捕获异常
- 功能最全,一个顶五个
使用场景:性能监控、缓存处理、事务控制、重试机制。
4.7 通知类型对比表
| 通知类型 | 执行时机 | 能否获取参数 | 能否获取返回值 | 能否阻止执行 | 能否修改返回值 |
|---|---|---|---|---|---|
| @Before | 方法执行前 | ✅ | ❌ | ❌ | ❌ |
| @AfterReturning | 方法返回后 | ✅ | ✅ | ❌ | ❌ |
| @AfterThrowing | 方法异常后 | ✅ | ❌ | ❌ | ❌ |
| @After | 方法结束后 | ✅ | ❌ | ❌ | ❌ |
| @Around | 包裹方法执行 | ✅ | ✅ | ✅ | ✅ |
五、切点表达式详解
5.1 execution 表达式(最常用)
// 语法 execution(返回类型 类路径.方法名 (参数)) // 示例 @Before("execution(* com.example.service.UserService.*(..))") // 匹配 UserService 的所有方法 @Before("execution(* com.example.service.*.*(..))") // 匹配 service 包下所有类的所有方法 @Before("execution(* com.example.service..*.*(..))") // 匹配 service 包及其子包下所有类的所有方法 @Before("execution(public * *(..))") // 匹配所有 public 方法 @Before("execution(* set*(..))") // 匹配所有以 set 开头的方法
通配符说明:
*:匹配任意字符(一个层级)..:匹配任意字符(多个层级)(..):匹配任意参数
5.2 within 表达式
@Before("within(com.example.service.UserService)") // 匹配指定类型 @Before("within(com.example.service.*)") // 匹配指定包 @Before("within(com.example.service..*)") // 匹配指定包及其子包
与 execution 的区别:
execution:匹配方法(粒度更细)within:匹配类型(粒度更粗)- 到包,到类层级,不用细化到方法
5.3 @annotation 表达式
-
常用,自定义AOP时便捷使用,灵活;按需标记
-
就是给目标方法/类,贴贴标签,声明式编程很大程度源于注解标注
@Before("@annotation(org.springframework.transaction.annotation.Transactional)") // 匹配带有 @Transactional 注解的方法 @Before("@annotation(com.example.annotation.Loggable)") // 匹配带有自定义注解的方法
实战案例:自定义注解 + 切面
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable { } @Aspect @Component public class LogAspect { @Around("@annotation(Loggable)") public Object log (ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); System.out.println(pjp.getSignature() + " 耗时 " + (System.currentTimeMillis() - start) + "ms" ); return result; } }
5.4 组合表达式
-
更强大,灵活,复用
-
各种方式使用且或非的方式获得更强的组合效果
// AND(且) @Before("execution(* com.example.service..(..)) && @annotation(Loggable)") // OR(或) @Before("execution(* com.example.service..(..)) || execution(* com.example.controller..(..))") // NOT(非) @Before("execution(* com.example.service..(..)) && !execution(* com.example.service.TestService.*(..))")
六、AOP 底层原理(核心章节)
6.1 两种代理方式(版本也有差异!)
Spring Boot 版本变化:
- Spring Boot 2.x(Spring 5):默认根据是否有接口智能选择(有接口→JDK,无接口→CGLIB)
- Spring Boot 3.x(Spring 6) :默认全部使用 CGLIB (
spring.aop.proxy-target-class=true) - 不需要实现接口
- 对开发者更透明
- 性能在现代 JVM 上已无明显差异
SpringBoot 3.2.x文档:
JDK 17+ 兼容性 :CGLIB 基于字节码生成代理类,在 JDK 16+ 模块化系统(JPMS)下可能遇到
InaccessibleObjectException。Spring Boot 3.x(基于 JDK 17+)中,如果 CGLIB 代理失败,可考虑以下方案:(1)使用 JDK 动态代理(为类实现接口);(2)升级 CGLIB 到最新版本或使用 ByteBuddy 替代;(3)添加 JVM 参数--add-opens java.base/java.lang=ALL-UNNAMED。
6.2 JDK 动态代理:完整源码分析
JDK 动态代理的核心类 :java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler。
Proxy.newProxyInstance 内部做了什么
// JDK 源码简化版(java.lang.reflect.Proxy) public static Object newProxyInstance (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { // 1. 校验 interfaces 数组中是否有非接口类型 for (Class<?> intf : interfaces) { if (!intf.isInterface()) { throw new IllegalArgumentException (); } } // 2. 查找或生成代理类(核心!) Class<?> proxyClass = getProxyClass0(loader, interfaces); // 3. 获取代理类的构造函数(参数为 InvocationHandler) Constructor<?> cons = proxyClass.getConstructor( new Class <?>[] { InvocationHandler.class }); // 4. 创建代理实例,传入 InvocationHandler return cons.newInstance( new Object [] { h }); }
关键步骤详解:
步骤 1 :getProxyClass0 内部做了三件事:
- 检查
interfaces是否超过 65535 个(JVM 限制) - 从
proxyClassCache中查找是否已存在该代理类(缓存避免重复生成) - 调用
ProxyGenerator.generateProxyClass生成字节码
步骤 2:生成的代理类长什么样?(反编译后的示例)
// 代理类 $Proxy0(JVM 动态生成的字节码,反编译后大致如此) public final class $Proxy0 extends Proxy implements UserService { private Method m1; // equals private Method m2; // hashCode private Method m3; // saveUser(目标接口方法) private Method m4; // getUser public $Proxy0(InvocationHandler h) { super (h); // 保存在 Proxy.h 中 } @Override public final String saveUser (String name) { // 关键!将方法调用委派给 InvocationHandler。这个方式的高明之处在于将代理的增强逻辑分离封装抽象了 // 为什么要把this自己传入进去,这里有点像传递上下文的感觉,就是代理对象本身的引用,没准增强逻辑的对象有用到。 return (String) super .h.invoke( this , m3, new Object []{name}); } @Override public final String getUser ( int id) { return (String) super .h.invoke( this , m4, new Object []{id}); } }
为什么 JDK 动态代理只能代理接口?
因为 Java 的单继承机制:代理类已经继承了 java.lang.reflect.Proxy,无法再继承目标类。所以只能通过实现接口来"冒充"目标对象。
进一步拓展思考!
这个代理对象,在spring体系下,IOC容器中,是一个bean,是我们平时依赖注入的bean,被增强过的bean,这样的话,容器中是不是有一大半代理对象bean都是Proxy的子类,是吧,代理老祖广收门徒,只要你有实现接口,我就能代理你,JDK动态代理如是说。
类比:JDK 动态代理就像"替身演员"------演员必须提前签好合同(定义接口),替身才能照着合同演。如果演员没签合同(没有接口),替身就不知道要演什么。
InvocationHandler.invoke 如何分发到目标方法
InvocationHandler代表执行JDK代理分离出来的增强逻辑。这个方式的高明之处在于将代理的增强逻辑分离封装抽象了;
而AopInvocationHandler是spring的InvocationHandler实现,关键在于Spring自己通过使用拦截器链的思想将多个执行逻辑也抽象封装了(并且按Order顺序执行)。
总结:JDK封装一次,Spring框架封装一次;接口就是抽象,就是与实现分离,达到扩展,方便业务差异化实现多个增强逻辑。
// Spring 内部的 InvocationHandler 实现(AopInvocationHandler 简化版) class AopInvocationHandler implements InvocationHandler { private final TargetSource targetSource; private final List<Object> chain; // 拦截器链 @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { // 1. 如果是 equals / hashCode,直接处理 if (AopUtils.isEqualsMethod(method)) { return proxy == args[ 0 ]; } if (AopUtils.isHashCodeMethod(method)) { return hashCode(); } // 2. 构建拦截器链,这个属于Spring框架层将众多的aop增强逻辑封装,因为该切点(目标方法)有可能存在多个增强逻辑(日志、事务、权限等) List<Object> chain = this .advised.getInterceptorsAndDynamicInterceptionAdvice( method, targetClass); // 3. 创建 MethodInvocation,执行责任链 MethodInvocation invocation = new ReflectiveMethodInvocation ( proxy, target, method, args, targetClass, chain); // 4. 从链头开始执行 return invocation.proceed(); } }
完整执行流程:
6.3 CGLIB 代理:完整源码分析
CGLIB 的核心类 :net.sf.cglib.proxy.Enhancer、net.sf.cglib.proxy.MethodInterceptor、net.sf.cglib.proxy.MethodProxy。
Enhancer.create 如何生成子类
// CGLIB 源码简化版(Enhancer.create 流程) public class Enhancer { public Object create () { // 1. 确定父类(目标类) if (superClass == null ) { throw new IllegalStateException ( "No superclass specified" ); } // 2. 使用 ASM 字节码框架生成子类 // 子类名 = 目标类名 + "$$EnhancerBySpringCGLIB$$" + hashCode,这是spring5;spring6则是"$$SpringCGLIB$$" Class<?> enhanced = ReflectUtils.defineClass( this .useFactory ? factoryName : null , this .superClass, this .interfaces, this .loadUseWeakClassCache, this .generator // 字节码生成器 ); // 3. 通过反射创建子类实例 return ReflectUtils.newInstance( enhanced, this .constructorArgs, this .constructor ); } }
生成的 CGLIB 子类长什么样?(反编译后)
-
spring5,springboot2.x,类名包括
$$EnhancerBySpringCGLIB$$ -
spring6,springboot3.x,类名调整为包括
$$SpringCGLIB$$// CGLIB 生成的子类(OrderService$$EnhancerBySpringCGLIB$$12345) public class OrderService$$EnhancerBySpringCGLIB$$12345 extends OrderService { // 回调数组 private MethodInterceptor CGLIBCALLBACK_0; @Override public String createOrder (String productId) { MethodInterceptor cb = this .CGLIBCALLBACK_0; if (cb != null ) { // 关键!将方法调用委派给 MethodInterceptor Object result = cb.intercept( this , CGLIBcreateOrder0Method, new Object []{productId}, CGLIBcreateOrder0Proxy); return (String) result; } return super .createOrder(productId); } // CGLIB 为每个方法预计算 MethodProxy private static Method CGLIBcreateOrder0Method; private static MethodProxy CGLIBcreateOrder0Proxy; static { // 通过 Signature 创建 MethodProxy CGLIBcreateOrder0Method = ReflectUtils.findMethods( "createOrder" , ...)[ 0 ]; CGLIBcreateOrder0Proxy = MethodProxy.create( OrderService.class, // 父类 OrderService$$EnhancerBySpringCGLIB$$ 12345. class, // 子类 "createOrder" ); } }
MethodInterceptor.intercept 的拦截流程
// Spring 内部的 DynamicAdvisedInterceptor(简化版) class DynamicAdvisedInterceptor implements MethodInterceptor { @Override public Object intercept (Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 1. 获取该方法的拦截器链 List<Object> chain = this .advised.getInterceptorsAndDynamicInterceptionAdvice( method, targetClass); // 2. 创建 CglibMethodInvocation CglibMethodInvocation invocation = new CglibMethodInvocation ( proxy, target, method, args, targetClass, chain, methodProxy); // 3. 执行责任链 return invocation.proceed(); } }
invokeSuper vs invoke 的区别
这是 CGLIB 中一个容易踩坑的地方:
// MethodProxy 简化源码 public class MethodProxy { // invoke:调用目标对象的原始方法 public Object invoke (Object target, Object[] args) throws Throwable { // 通过反射调用 target 上的方法 // 如果 target 是代理对象,会再次触发拦截! return invokeInit.get().invoke(target, args); } // invokeSuper:调用父类(即被代理的原始类)的方法 public Object invokeSuper (Object obj, Object[] args) throws Throwable { // 通过 FastClass 机制直接调用父类方法 // 不经过反射,不走拦截器,更快 return invokeSuperInit.get().invoke(obj, args); } }
关键区别:
| 方法 | 调用目标 | 是否经过拦截器 | 性能 |
|---|---|---|---|
| invoke | 目标对象(可能是代理) | 可能再次触发拦截 | 反射,较慢,这是统一操作 |
| invokeSuper | 父类原始方法 | 不经过拦截器 | FastClass 直接调用,更快,因为有些方法不用代理 |
一个类中有@Transactional事务注解,那么这个目标对象bean就会多出个代理对象bean,而最终依赖注入的这个bean则变为代理对象(会持有目标对象的引用)。但是原本对象上只有个别方法是添加了事务注解的,而查询类的方法一般不会加,所以在处理的时候代理对象bean选择直接调用目标对象也就是原始父类的方法invokeSuper,省了一些反射操作。
为什么 invokeSuper 更快?
CGLIB 使用了 FastClass 机制:为每个方法生成一个索引表,通过索引直接调用方法,避免了反射开销。本质上是把反射调用变成了类似 switch-case 的直接调用。
// FastClass 简化示意 class FastClassForOrderService { public Object invoke ( int index, Object obj, Object[] args) { OrderService self = (OrderService) obj; switch (index) { case 0 : return self.createOrder((String) args[ 0 ]); // 直接调用,无反射 case 1 : return self.getOrder((Integer) args[ 0 ]); default : throw new IllegalArgumentException (); } } }
final 方法为什么不能被 CGLIB 代理
CGLIB 通过生成目标类的子类来实现代理,子类必须重写父类方法才能插入拦截逻辑。final 方法不能被重写,所以 CGLIB 无法为 final 方法生成代理逻辑。
// final 方法在 CGLIB 子类中不会被重写 public class OrderService$$EnhancerBySpringCGLIB$$12345 extends OrderService { // createOrder 被重写 -> 有拦截逻辑 @Override public String createOrder (String productId) { // ... 拦截逻辑 ... } // finalMethod 不被重写 -> 直接继承父类实现 // 所以 final 方法的调用不经过 MethodInterceptor }
6.4 拦截器链执行原理(责任链模式)
这是 Spring AOP 最核心的机制之一。
CglibMethodInvocation.proceed 的责任链模式
// ReflectiveMethodInvocation 简化源码(JDK 代理和 CGLIB 代理共用类似逻辑) public class ReflectiveMethodInvocation implements MethodInvocation { protected final Object proxy; protected final Object target; protected final Method method; protected final Object[] arguments; protected List<Object> interceptorsAndDynamicMethodMatchers; // 当前执行到的拦截器索引 private int currentInterceptorIndex = - 1 ; @Override public Object proceed () throws Throwable { // 1. 所有拦截器都已执行,调用目标方法 if ( this .currentInterceptorIndex == this .interceptorsAndDynamicMethodMatchers.size() - 1 ) { return invokeJoinpoint(); // method.invoke(target, arguments) } // 2. 获取下一个拦截器 Object interceptorOrInterceptionAdvice = this .interceptorsAndDynamicMethodMatchers.get(++ this .currentInterceptorIndex); // 3. 是 InterceptorAndDynamicMethodMatcher?做参数匹配 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; if (dm.methodMatcher.matches(method, targetClass, arguments)) { return dm.interceptor.invoke( this ); // 匹配成功,执行拦截器 } return proceed(); // 匹配失败,跳过 } // 4. MethodInterceptor,执行拦截器(会再次调用 this.proceed()) return ((MethodInterceptor) interceptorOrInterceptionAdvice) .invoke( this ); } protected Object invokeJoinpoint () throws Throwable { return AopUtils.invokeJoinpointUsingReflection(target, method, arguments); } }
执行流程图 :
关键理解:
proceed()每次将currentInterceptorIndex加 1- 每个
MethodInterceptor.invoke内部调用invocation.proceed()才能继续链的后续节点 - @Around 必须调用
pjp.proceed(),否则拦截器链在当前位置中断,后面的通知和目标方法都不会执行
类比 :拦截器链像一条流水线,proceed() 是把产品传送到下一道工序。如果某个工序扣留了产品(不调用 proceed),后面的工序就收不到。
多个通知如何组成拦截器链
Spring 将每种通知类型封装为对应的拦截器:
| 通知类型 | 拦截器实现类 |
|---|---|
| @Around | AspectJAroundAdvice → MethodInterceptor |
| @Before | AspectJMethodBeforeAdvice → MethodInterceptor |
| @AfterReturning | AspectJAfterReturningAdvice → MethodInterceptor |
| @AfterThrowing | AspectJAfterThrowingAdvice → MethodInterceptor |
| @After | AspectJAfterAdvice → MethodInterceptor |
所有通知都被包装为 MethodInterceptor,按顺序加入拦截器链,由 proceed() 统一调度。方法拦截器,还挺形象的,本质确实就是给目标方法前前后后,左左右右增强。
6.5 AnnotationAwareAspectJAutoProxyCreator 工作原理
这是 Spring AOP 的"幕后黑手"------所有代理的创建都经过它。
从 @EnableAspectJAutoProxy 到注册
@EnableAspectJAutoProxy 源码:
@Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { boolean proxyTargetClass () default false ; // 是否强制 CGLIB,true表示cglig,false表示有接口就jdk,无则cglib;springboot3中yml配置优先级更高!!! boolean exposeProxy () default false ; // 是否暴露代理到 AopContext }
AspectJAutoProxyRegistrar 在容器注册了一个 AnnotationAwareAspectJAutoProxyCreator
一个非常强大的bean,他是其他普通bean的代理检查员,所有bean都需要给他检验判断一下,是否需要升级替换为代理bean。
postProcessAfterInitialization 中如何判断是否需要创建代理
// AbstractAutoProxyCreator.postProcessAfterInitialization 简化流程 @Override public Object postProcessAfterInitialization ( @Nullable Object bean, String beanName) { if (bean != null ) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (! this .earlyProxyReferences.contains(cacheKey)) { // 核心:决定是否创建代理 return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } protected Object wrapIfNecessary (Object bean, String beanName, Object cacheKey) { // 1. 跳过:已处理过、infra 类、shouldSkip 的类 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { return bean; } // 2. 获取所有适用于该 Bean 的 Advisor(切面通知器) Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null ); // 3. 如果有 Advisor 适用于该 Bean,创建代理 if (specificInterceptors != DO_NOT_PROXY) { this .advisedBeans.put(cacheKey, Boolean.TRUE); // 创建代理 return createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource (bean)); } return bean; }
AspectJ Advisor 的解析和排序:
// AnnotationAwareAspectJAdvisorFactory 简化流程 public List<Advisor> getAdvisors (MetadataAwareAspectInstanceFactory aspectInstanceFactory) { List<Advisor> advisors = new ArrayList <>(); // 遍历 @Aspect 类中的所有方法 for (Method method : getAdvisorMethods(aspectClass)) { // 跳过 @Pointcut 标注的方法 if (isPointcut(method)) continue ; // 解析通知类型(@Before / @Around / @After 等) AspectJAnnotation<?> annotation = getAspectJAnnotation(method); if (annotation == null ) continue ; // 创建对应的 Advisor Advisor advisor = createAdvisor(method, declarationOrder, aspectInstanceFactory); advisors.add(advisor); } // 按 @Order 排序 AnnotationAwareOrderComparator.sort(advisors); return advisors; }
6.6 @EnableAspectJAutoProxy(exposeProxy=true) 底层实现
AopContext 的 ThreadLocal 机制
// org.springframework.aop.framework.AopContext 源码,版本:spring6.x public final class AopContext { private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal ( "Current AOP proxy" ); private AopContext () { } public static Object currentProxy () throws IllegalStateException { Object proxy = currentProxy.get(); if (proxy == null ) { throw new IllegalStateException ( "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context." ); } else { return proxy; } } @Nullable static Object setCurrentProxy ( @Nullable Object proxy) { Object old = currentProxy.get(); if (proxy != null ) { currentProxy.set(proxy); } else { currentProxy.remove(); } return old; } }
currentProxy() 如何获取当前代理
在代理对象的方法调用入口处,Spring 会将代理对象设置到 AopContext 中:
// CglibAopProxy.DynamicAdvisedInterceptor 简化 class DynamicAdvisedInterceptor implements MethodInterceptor { @Override public Object intercept (Object proxy, Method method, Object[] args, MethodProxy methodProxy) { Object oldProxy = null ; if ( this .advised.exposeProxy) { // 将当前代理设置到 ThreadLocal oldProxy = AopContext.setCurrentProxy(proxy); } try { // 执行拦截器链... Object result = proceedChain(); return result; } finally { // 清理 ThreadLocal,避免内存泄漏 if ( this .advised.exposeProxy) { AopContext.setCurrentProxy(oldProxy); } } } }
setCurrentProxy(oldProxy) 设计哲学
关键设计:setCurrentProxy(oldProxy),为什么不使用remove()?
核心原因:支持嵌套代理调用
场景示例
@Service public class ServiceA { @Autowired private ServiceB serviceB; @Transactional public void methodA () { // 此时 AopContext 中存储的是 ServiceA 的代理 serviceB.methodB(); // 嵌套调用 ServiceB } } @Service public class ServiceB { @Transactional public void methodB () { // 此时 AopContext 中应该存储的是 ServiceB 的代理 Object proxy = AopContext.currentProxy(); // 需要获取 ServiceB 的代理 } }
执行流程详解
1. 进入 ServiceA.methodA()
oldProxy = null ; // 之前没有代理 AopContext.setCurrentProxy(ServiceA的代理); // 设置 ServiceA 代理 // ThreadLocal: [ServiceA代理]
2. 嵌套调用 ServiceB.methodB()
oldProxy = ServiceA的代理; // 保存外层代理 AopContext.setCurrentProxy(ServiceB的代理); // 设置 ServiceB 代理 // ThreadLocal: [ServiceB代理] (覆盖了 ServiceA 代理) // 执行 ServiceB 的业务逻辑 Object proxy = AopContext.currentProxy(); // 获取到 ServiceB 的代理 ✅
3. ServiceB.methodB() 执行完毕,清理资源
AopContext.setCurrentProxy(ServiceA的代理); // 恢复为 ServiceA 的代理 ✅ // ThreadLocal: [ServiceA代理] (恢复外层上下文) // 而不是 remove(),因为外层还在使用中
4. ServiceA.methodA() 执行完毕,清理资源
AopContext.setCurrentProxy( null ); // oldProxy 是 null,相当于 remove // ThreadLocal: [] (完全清空)
为什么不能用 remove()?
如果使用 remove():
// ServiceB.methodB() 执行完毕 if (setProxyContext) { AopContext.currentProxy.remove(); // ❌ 错误! }
问题 :此时 ServiceA.methodA() 还没有执行完,它还需要通过 AopContext.currentProxy() 获取自己的代理对象。如果直接 remove(),ServiceA 后续调用 AopContext.currentProxy() 就会抛出 IllegalStateException。
setCurrentProxy 的设计精妙之处
看源码:
@Nullable static Object setCurrentProxy ( @Nullable Object proxy) { Object old = currentProxy.get(); // 保存旧值 if (proxy != null ) { currentProxy.set(proxy); // 设置新值 } else { currentProxy.remove(); // proxy 为 null 时才 remove } return old; // 返回旧值,供恢复使用 }
关键设计:
- 传入
null时才会真正remove() - 传入具体对象时会
set() - 始终返回旧值,形成"栈"的效果
类比理解:函数调用栈
这就像函数调用栈的压栈和弹栈:
methodA() 调用 → 压栈 ServiceA 代理 methodB() 调用 → 压栈 ServiceB 代理 methodB() 返回 → 弹栈,恢复 ServiceA 代理 methodA() 返回 → 弹栈,清空
如果用 remove() 就相当于直接把整个栈清空了,外层的上下文就丢失了。
总结对比
| 方式 | 效果 | 适用场景 |
|---|---|---|
| setCurrentProxy(oldProxy) | 恢复上一层代理 | ✅ 支持嵌套调用 |
| currentProxy.remove() | 完全清空 | ❌ 会破坏外层上下文 |
所以这里必须用 setCurrentProxy(oldProxy) 来恢复调用前的状态 ,而不是简单地删除。这是典型的栈式管理思想。
为什么默认关闭(性能考虑)
ThreadLocal.set/get有开销- 每次方法调用都要设置和清理
- 大多数场景不需要同类调用访问代理
类比 :exposeProxy 就像在每间办公室门口挂一个"当前代理人"的牌子。默认不挂(省成本),需要的时候再挂。
6.7 AspectJ 编译时织入 vs Spring AOP 运行时代理
AspectJ 的三种织入方式
| 织入方式 | 时机 | 说明 |
|---|---|---|
| 编译时织入(CTW) | javac 编译期 | 使用 ajc 编译器,在编译时将切面代码直接写入目标类字节码 |
| 后编译时织入(Post-compile Weaving) | .class 文件生成后 | 对已有的 .class 或 .jar 进行织入 |
| 加载时织入(LTW) | ClassLoader 加载时 | 通过 -javaagent 在类加载时修改字节码 |
对比 Spring AOP
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 织入时机 | 运行时(代理) | 编译时/加载时 |
| 代理方式 | JDK/CGLIB | 直接修改字节码 |
| 方法级别 | ✅ | ✅ |
| 构造器级别 | ❌ | ✅ |
| 字段级别 | ❌ | ✅(field get/set) |
| static 方法 | ❌ | ✅ |
| final 方法 | ❌(CGLIB) | ✅ |
| 同类调用 | ❌(不经过代理) | ✅(直接织入) |
| 性能 | 有代理开销 | 无代理开销 |
| 使用复杂度 | 低(注解即可) | 高(需要 ajc 或 LTW agent) |
Spring AOP 为什么只支持方法级别的切面?
因为 Spring AOP 基于代理,代理只能拦截"通过代理对象调用方法"这个行为。字段访问、构造器调用、静态方法都不走代理对象的方法调用路径,所以无法拦截。
类比:
- Spring AOP 像"门卫"------只能拦截从大门进出的人(方法调用)
- AspectJ 像"隐形改造"------直接把规则写入建筑内部(字节码),任何角落都能覆盖
6.8 springboot3.x其AOP默认代理
元数据:spring-configuration-metadata.json
具体默认值:
{ "name" : "spring.aop.auto" , "type" : "java.lang.Boolean" , "description" : "Add @EnableAspectJAutoProxy." , "defaultValue" : true } , { "name" : "spring.aop.proxy-target-class" , "type" : "java.lang.Boolean" , "description" : "Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false)." , "defaultValue" : true } ,
所以application.yml,默认就是true,使用cglib;不想则得显式改为false
# Spring AOP 配置 spring: aop: # 强制使用 JDK 动态代理(目标类有接口时) # proxy-target-class: true 表示使用 CGLIB(Spring Boot 3.x 默认) # proxy-target-class: false 表示优先使用 JDK 动态代理 proxy-target-class: false
七、AOP 的应用场景
7.1 日志记录(最常用)
@Aspect @Component public class LogAspect { private static final Logger log = LoggerFactory.getLogger(LogAspect.class); @Around("@annotation(Loggable)") public Object logExecutionTime (ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); log.info( "方法执行:{}.{}" , pjp.getSignature().getDeclaringTypeName(), pjp.getSignature().getName()); try { Object result = pjp.proceed(); long elapsed = System.currentTimeMillis() - start; log.info( "方法完成:{}ms" , elapsed); return result; } catch (Throwable e) { log.error( "方法异常" , e); throw e; } } }
7.2 缓存处理
@Aspect @Component public class CacheAspect { @Autowired private CacheManager cacheManager; @Around("@annotation(Cacheable)") public Object cacheable (ProceedingJoinPoint pjp) throws Throwable { String key = generateKey(pjp); Object cached = cacheManager.get(key); if (cached != null ) { return cached; // 缓存命中,不调用 proceed() } Object result = pjp.proceed(); cacheManager.put(key, result); return result; } }
7.3 权限校验
@Aspect @Component public class AuthAspect { @Around("@annotation(RequiresPermission)") public Object checkPermission (ProceedingJoinPoint pjp) throws Throwable { MethodSignature sig = (MethodSignature) pjp.getSignature(); RequiresPermission anno = sig.getMethod().getAnnotation(RequiresPermission.class); if (!hasPermission(anno.value())) { throw new AccessDeniedException ( "无权限:" + anno.value()); } return pjp.proceed(); } }
八、AOP 失效的常见陷阱
陷阱 1:同类方法调用(最高频)
@Service public class UserService { public void createUser () { // 直接调用 saveUser,AOP 不生效! saveUser(); } @Transactional public void saveUser () { userRepository.save(); } }
问题原因:
- Spring AOP 基于代理
- 同类方法调用是
this.saveUser(),走的是目标对象自身,不经过代理 - 所以
saveUser()的事务注解不生效
类比:你雇了一个秘书(代理)帮你处理文件,但你给自己递文件时,秘书看不到。
解决方案:
// 方案 1:注入自己(推荐,最简洁) @Service public class UserService { @Autowired @Lazy private UserService self; public void createUser () { self.saveUser(); // 通过代理调用 } @Transactional public void saveUser () { userRepository.save(); } } // 方案 2:AopContext(需要 exposeProxy=true) @Service public class UserService { public void createUser () { ((UserService) AopContext.currentProxy()).saveUser(); } @Transactional public void saveUser () { userRepository.save(); } } // 方案 3:拆分到不同 Service(最干净) @Service class UserService { @Autowired private UserSaveService saveService; public void createUser () { saveService.saveUser(); // 跨 Bean 调用,走代理 } } @Service class UserSaveService { @Transactional public void saveUser () { userRepository.save(); } }
陷阱 2:非 public 方法
@Service public class MyService { // ❌ 不生效 @Transactional private void privateMethod () {} // ❌ 不生效(Spring AOP 默认只代理 public,CGLIB 下 protected 部分支持) @Transactional protected void protectedMethod () {} // ✅ 生效 @Transactional public void publicMethod () {} }
原因:Spring AOP 的代理只对 public 方法保证完整支持。private 方法不可见,protected 方法在 JDK 代理下也不可用。
陷阱 3:静态方法
@Service public class MyService { // ❌ 不生效 @Transactional public static void staticMethod () {} }
原因:static 方法属于类,不属于对象,无法通过代理对象调用。
陷阱 4:final 方法
@Service public class MyService { // ❌ 不生效(CGLIB 无法继承 final 方法) @Transactional public final void finalMethod () {} }
原因:CGLIB 通过继承实现代理,final 方法无法被重写。
陷阱 5:自调用(直接 new 对象)
// ❌ 直接 new,不是代理 OrderService service = new OrderService (); service.createOrder(); // 事务不生效! // ✅ 从容器获取 @Autowired private OrderService service; // 这是代理对象 service.createOrder(); // 事务生效
九、切面执行顺序
9.1 @Order 注解
@Aspect @Order(1) // 优先级高,先执行 @Component public class AuthAspect { @Around("...") public Object auth (ProceedingJoinPoint pjp) throws Throwable { System.out.println( "1. 权限检查" ); return pjp.proceed(); } } @Aspect @Order(2) // 优先级低,后执行 @Component public class LogAspect { @Around("...") public Object log (ProceedingJoinPoint pjp) throws Throwable { System.out.println( "2. 日志记录" ); return pjp.proceed(); } }
规则 :@Order 值越小,优先级越高,越先执行其 @Around-before 和 @Before。
9.2 Ordered 接口
@Aspect @Component public class MyAspect implements Ordered { @Override public int getOrder () { return 1 ; } }
9.3 洋葱模型
多个切面的环绕通知形成"洋葱"结构:
Order(1)-before → Order(2)-before → 目标方法 → Order(2)-after → Order(1)-after
外层切面的 before 先执行,但 after 后执行(逆序回退)。
十、常见误区
误区 1:认为 @Around 比 @Before 慢
不一定! @Around 实际上性能更好:
- @Around 只创建一个通知实例
- @Before + @AfterReturning + @AfterThrowing 组合会产生多个通知实例和多次拦截器链遍历
- @Around 一次拦截搞定所有逻辑,拦截器链更短
实测:在高频调用场景下,单个 @Around 通常比等效的 @Before + @AfterReturning 快 10%-20%。
误区 2:认为 AOP 可以代理 static 方法
不能! static 方法属于类,不属于对象实例。代理对象只能拦截通过它调用的实例方法。
// ❌ 以下切面不会生效 @Aspect public class MyAspect { @Before("execution(static * com.example.Service.myStaticMethod(..))") public void before () {} // 永远不会触发 }
如果需要拦截静态方法,必须使用 AspectJ 的编译时织入(CTW)。
误区 3:认为同类调用 @Transactional 生效
不生效! 这是 Spring AOP 最常见的陷阱(见第八章陷阱 1)。
同类内部调用走 this,不经过代理。解决方案:
-
- 拆分到不同 Service
-
- 注入
@Lazy的自己
- 注入
-
AopContext.currentProxy()
误区 4:认为切面执行顺序只由 @Order 决定
不完整! 切面排序受多种因素影响:
| 排序方式 | 优先级 |
|---|---|
| @Order 注解 | ✅ |
| 实现 Ordered 接口 | ✅ |
| @jakarta.annotation.Priority | ✅ |
| 未标注任何排序 | 默认 Ordered.LOWEST_PRECEDENCE (最后) |
实际排序规则 (AnnotationAwareOrderComparator):
-
- 优先使用
@Order的 value
- 优先使用
-
- 其次使用
Ordered.getOrder()
- 其次使用
-
- 最后使用
@Priority
- 最后使用
-
- 都没有则排到最后
误区 5:认为 JDK 动态代理一定比 CGLIB 慢
过时认知! 在 JDK 8+ 之后:
- JDK 动态代理使用
LambdaMetafactory生成字节码,性能大幅提升 - CGLIB 因为要生成子类字节码,初始化开销更大
- 高频调用场景下,两者性能差异已很小
Spring Boot 3.x 默认 CGLIB 的原因:不是性能,而是更方便------不需要定义接口。
十一、最佳实践
1. 优先使用 @Around 处理复杂逻辑
// ✅ 推荐:一个 @Around 搞定所有 @Around("execution(* com.example.service.*.*(..))") public Object around (ProceedingJoinPoint pjp) throws Throwable { long start = System.nanoTime(); try { Object result = pjp.proceed(); log.info( "成功:{} {}ns" , pjp.getSignature(), System.nanoTime() - start); return result; } catch (Throwable e) { log.error( "异常:{}" , pjp.getSignature(), e); throw e; } } // ❌ 不推荐:@Before + @AfterReturning + @AfterThrowing 分开写
2. 避免在切面中使用 @Autowired 的懒加载问题
@Aspect @Component public class MyAspect { // 切面本身也是 Bean,@Autowired 正常使用 @Autowired private SomeService someService; }
3. 同类调用优先拆分 Service
// ✅ 推荐:拆分到不同 Bean @Service class OrderCreateService { @Autowired private OrderSaveService saveService; public void create () { saveService.save(); // 走代理 } } @Service class OrderSaveService { @Transactional public void save () { ... } } // ⚠️ 备选:注入自己(有循环依赖风险) @Autowired @Lazy private SelfService self;
4. 使用自定义注解代替复杂切点表达式
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Audit { String action () default "" ; } @Aspect @Component public class AuditAspect { @Around("@annotation(audit)") public Object audit (ProceedingJoinPoint pjp, Audit audit) throws Throwable { // 直接获取注解参数 System.out.println( "操作:" + audit.action()); return pjp.proceed(); } }
5. 使用 exposeProxy 解决同类调用的注意事项
# application.properties spring.aop.auto-proxy-expose-proxy = true
// 使用 AopContext.currentProxy() 时注意: // 1. 有 ThreadLocal 开销 // 2. 非代理环境下会抛 IllegalStateException // 3. 方法结束后自动清理,不需要手动 remove
十二、选型决策树
JDK vs CGLIB 对比:
| 维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 代理方式 | 实现接口 | 继承子类 |
| 需要接口 | ✅ | ❌ |
| final 类 | 不影响 | ❌ 无法代理 |
| final 方法 | 不影响 | ❌ 无法代理 |
| private 方法 | 不涉及 | ❌ 无法代理 |
| 性能 | 好 | 好(FastClass) |
| 初始化开销 | 低 | 中(生成字节码) |
| Spring Boot 2.x 默认 | ✅(有接口时) | ✅(无接口时) |
| Spring Boot 3.x 默认 | ❌(需配置) | ✅(强制默认) |
十三、单元测试(结果)
以下为测试结果,具体代码基于 Spring Boot 3.x + Spring 6,可直接在项目中运行,验证 AOP 的各种行为。
代码:Spring AOP 原理全解析(单元测试代码),处于副文章位置
重要说明 :本文档的测试代码已针对 Spring Boot 3.x(Spring 6)进行调整。如果你使用的是 Spring Boot 2.x(Spring 5),需要注意以下差异:
通知执行顺序 :Spring 5 中
@Around-after在@AfterReturning之前执行默认代理策略 :Spring Boot 2.x 默认根据接口智能选择 JDK/CGLIB,而 3.x 默认全部使用 CGLIB
CGLIB 类名格式 :Spring Boot 2.x 为
$$EnhancerBySpringCGLIB$$,3.x 为$$SpringCGLIB$$具体单元测试代码,请查看第二篇文章:《单元测试 | Spring AOP原理全解析》
测试 1:AopProxyTest
测试 2:AopFailureTest
测试 3:AspectOrderTest
十四、Spring 5 vs Spring 6 核心差异总结
14.1 通知执行顺序变化
| 特性 | Spring 5 (Boot 2.x) | Spring 6 (Boot 3.x) |
|---|---|---|
| @Around-after 位置 | 在 @AfterReturning 之前 | 在所有通知之后(最后) |
| 完整顺序 | Around-before → Before → 目标方法 → Around-after → AfterReturning → After | Around-before → Before → 目标方法 → AfterReturning → After → Around-after |
| 设计理念 | Around 的后置部分包裹 AfterReturning | Around 真正环绕整个连接点(包括所有其他通知) |
| 兼容性 | - | 更符合 AspectJ 标准语义 |
为什么 Spring 6 要调整?
- 让
@Around真正"环绕"整个 Join Point,包括其他所有通知 - 更符合 AspectJ 的标准语义
@Around可以观察到完整的执行过程(包括其他通知的执行)
14.2 默认代理策略变化
| 特性 | Spring Boot 2.x (Spring 5) | Spring Boot 3.x (Spring 6) |
|---|---|---|
| 默认策略 | 智能选择: - 有接口 → JDK 动态代理 - 无接口 → CGLIB | 强制 CGLIB (即使有接口) |
| 配置项默认值 | proxy-target-class: false | proxy-target-class: true |
| CGLIB 类名格式 | $$EnhancerBySpringCGLIB$$ | $$SpringCGLIB$$ |
| 如何启用 JDK 代理 | 默认即可(有接口时) | 需显式配置: spring.aop.proxy-target-class=false |
为什么 Spring Boot 3 要改?
-
- 性能考虑:CGLIB 在现代 JVM 上性能已经优于或等同于 JDK 动态代理
-
- 简化行为:统一使用一种代理方式,减少不确定性
-
- 功能一致:CGLIB 可以代理所有类,而 JDK 只能代理接口
-
- Spring Framework 6 的推动:Spring 6 本身也倾向于推荐使用 CGLIB
14.3 迁移指南
如果你从 Spring Boot 2.x 升级到 3.x,需要注意:
需要调整的地方
-
- 测试代码中的代理类名检查
// Spring Boot 2.x assertTrue(className.contains( "$$EnhancerBySpringCGLIB" )); // Spring Boot 3.x assertTrue(className.contains( "$$SpringCGLIB" ));
-
- 通知执行顺序的断言
// Spring Boot 2.x assertEquals( "@Around-after" , order.get( 2 )); assertEquals( "@AfterReturning" , order.get( 3 )); // Spring Boot 3.x assertEquals( "@AfterReturning" , order.get( 2 )); assertEquals( "@Around-after" , order.get( 4 )); // 移到最后
-
- 如果需要 JDK 动态代理
application.yml spring: aop: proxy-target-class: false # 覆盖 Spring Boot 3 的默认行为
不需要调整的地方
- AOP 的基本用法(注解、切点表达式等)完全兼容
- 拦截器链的执行机制没有变化
- AOP 失效场景(同类调用、final、static 等)依然相同
14.4 版本选择建议
| 场景 | 推荐版本 | 原因 |
|---|---|---|
| 新项目 | Spring Boot 3.x + Spring 6 | 最新稳定版,长期支持 |
| 已有项目升级 | 评估影响后升级 | 注意通知顺序和代理策略变化 |
| 需要 JDK 代理 | 两者均可 | 2.x 默认支持,3.x 需配置 |
| 追求稳定性 | Spring Boot 2.7.x | 最后一个 2.x 版本,LTS |
十五、总结
核心要点速查
| 要点 | 说明 |
|---|---|
| 五种通知 | @Before、@AfterReturning、@AfterThrowing、@After、@Around |
| 两种代理 | JDK 动态代理(接口)、CGLIB(继承子类) |
| 切点表达式 | execution、within、@annotation、@within、args |
| 拦截器链 | 责任链模式,proceed() 驱动 |
| 常见失效 | 同类调用、非 public、静态方法、final 方法 |
| 排序机制 | @Order 值越小优先级越高 |
| exposeProxy | AopContext.ThreadLocal 存储当前代理 |
最佳实践清单
- 优先使用 @Around 处理复杂逻辑(一个顶五个)
- 自定义注解 + @Around 是最佳拍档
- 同类调用优先拆分 Service,避免 self 引用
- 切面中避免做重量级操作(每个方法都会执行)
- 使用
!within(*..*Test*)排除测试类 - 了解 AOP 失效场景,避免踩坑
十六、面试自测
基础知识
- 能说出 JDK 动态代理和 CGLIB 的区别
- 能解释 Proxy.newProxyInstance 的内部流程
- 能说明 InvocationHandler.invoke 如何将调用分发给目标方法
- 能解释 MethodProxy.invokeSuper 和 invoke 的区别
- 能画出拦截器链(责任链)的执行流程
- 能解释为什么 @Around 必须调用 pjp.proceed()
- 能说明 AnnotationAwareAspectJAutoProxyCreator 的工作流程
- 能解释 AopContext 的 ThreadLocal 机制
- 能说出自调用失效的原因和至少两种解决方案
- 能解释 final/static/private 方法为什么不能被代理
- 能说清 @Order 控制切面优先级的原理
- 能区分 Spring AOP 和 AspectJ 的能力边界
- 能解释为什么 @Around 通常比 @Before 更快
- 能说清切面排序的多因素规则(@Order、Ordered、@Priority)
Spring 5 vs Spring 6 差异专项
- 能说出 Spring 5 和 Spring 6 的通知执行顺序差异
- 能解释为什么 Spring 6 要调整 @Around-after 的位置
- 能说清 Spring Boot 2.x 和 3.x 的默认代理策略区别
- 知道如何在 Spring Boot 3.x 中启用 JDK 动态代理
- 了解 CGLIB 类名格式在两个版本中的变化
十七、参考资料
Spring 官方文档
- Spring 5 文档:AspectJ Support (5.x)
- Spring 6 文档:AspectJ Support (6.x)
- Spring Boot 2.x:AOP Features
- Spring Boot 3.x:AOP Features
Spring 源码
org.springframework.aop.framework.ProxyFactoryorg.springframework.aop.framework.AbstractAutoProxyCreatororg.springframework.aop.framework.ReflectiveMethodInvocationorg.springframework.aop.framework.CglibAopProxyorg.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreatororg.springframework.aop.framework.AopContext
JDK & CGLIB 源码
- JDK 源码:
java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler - CGLIB 源码:
net.sf.cglib.proxy.Enhancer、net.sf.cglib.proxy.MethodProxy
书籍
- 《Spring 实战》第 5 章
- 《Spring 源码深度解析》第 8 章 - AOP
- 《Spring Boot 实战》- 版本差异章节
迁移指南
- Spring Boot 3.0 Migration Guide
- Spring Framework 6.0 Migration Guide