目录
一、核心前提
二、JDK 动态代理(底层原理)
1. 核心类
2. 执行流程
3. 关键特点
三、CGLIB 动态代理(底层原理)
1. 核心原理
2. 核心类
3. 执行流程
4. 关键特点
四、Spring AOP 完整执行流程(容器角度)
五、AOP 通知执行顺序(底层调用链)
六、常见面试关键点总结
极简一句话总结
Spring AOP默认基于动态代理实现,分两种场景:JDK 动态代理、CGLIB 动态代理,不使用原生 AspectJ 编译织入,是运行时增强。
一、核心前提
- AOP 本质:不修改原业务代码,在方法执行前后 / 异常 / 返回做增强(日志、事务、权限、监控等)。
- 两大代理选择规则
- 目标类实现了接口:默认使用JDK 动态代理
- 目标类没有接口/ 强制代理类:使用CGLIB 动态代理
- Spring 版本变化:
- Spring 3.2+ 内置 CGLIB,无需额外引入包
- Spring Boot 2.x+默认强制使用 CGLIB(不再优先 JDK)
二、JDK 动态代理(底层原理)
1. 核心类
java.lang.reflect.Proxy+java.lang.reflect.InvocationHandler只能代理接口,无法代理普通类。
2. 执行流程
- 运行时,JDK 动态生成代理类字节码,代理类和目标类实现同一个接口。
- 调用代理对象方法 → 触发
InvocationHandler.invoke()。 - 在
invoke中执行前置通知 → 目标方法 → 后置 / 异常通知。
3. 关键特点
- 基于接口,代理类和目标类是兄弟关系(同接口)
- 性能中等,JDK 原生,无第三方依赖
- 不能代理 final 方法、私有方法
三、CGLIB 动态代理(底层原理)
1. 核心原理
CGLIB(Code Generation Library)字节码增强,底层依赖 ASM 框架。通过继承目标类生成子类,用子类重写父类方法实现拦截增强。
2. 核心类
Enhancer(生成代理类) +MethodInterceptor(方法拦截器)
3. 执行流程
- 运行时动态生成目标类的子类作为代理类。
- 子类重写父类所有非
final方法。 - 调用方法时进入
MethodInterceptor.intercept(),执行 AOP 增强逻辑 + 原方法。
4. 关键特点
- 基于继承,可以代理普通类、无接口类
- 无法代理:
final类、final方法、private方法(无法重写) - 早期版本创建代理实例慢,方法调用快;现在优化后差距很小
四、Spring AOP 完整执行流程(容器角度)
- Bean 实例化阶段Spring 扫描切面、切点、通知,解析
@Aspect、@Pointcut等注解。 - 判断代理类型检查目标 Bean 是否有接口 → 选 JDK / CGLIB。
- 创建代理对象容器不返回原目标对象,而是返回代理对象存入 IoC 容器。
- 方法调用外部调用 Bean 方法 → 进入代理拦截器 → 执行 AOP 通知链 → 执行原方法。
五、AOP 通知执行顺序(底层调用链)
正常执行:前置通知 → 环绕通知 (前) → 目标方法 → 环绕通知 (后) → 返回通知 → 后置通知
异常执行:前置通知 → 环绕通知 (前) → 目标方法抛异常 → 异常通知 → 后置通知
六、常见面试关键点总结
- 为什么 JDK 只能代理接口?JDK 代理设计就是面向接口,生成的代理类必然实现目标接口,无法继承普通类。
- CGLIB 为什么不能代理 final 类 / 方法?CGLIB 靠继承 + 重写,final 禁止重写,无法拦截。
- Spring 如何切换代理模式?
- XML:
<aop:proxy-target-class="true"/>强制 CGLIB - 注解 / 配置类:
@EnableAspectJAutoProxy(proxyTargetClass = true)
- XML:
- Spring AOP vs 原生 AspectJ
- Spring AOP:运行时动态代理,轻量、侵入低,功能有限
- AspectJ:编译期 / 加载期织入,修改字节码,功能更强,需额外编译插件
极简一句话总结
Spring AOP 底层就是运行时动态代理:有接口用 JDK 接口代理,无接口用 CGLIB 子类继承代理,在方法拦截中织入切面增强逻辑。