外观
一句话答案
JDK 代理基于接口(Proxy+InvocationHandler),CGLIB 基于继承(字节码生成子类),Spring 默认有接口用 JDK 否则用 CGLIB。
核心要点
AOP(Aspect-Oriented Programming,面向切面编程):
- 通过代理模式,在不修改原始代码的情况下,在方法调用前后插入横切逻辑(日志、事务、权限等)
Spring AOP 的两种代理机制:
1. JDK 动态代理(interface-based)
java
// 要求:目标类必须实现至少一个接口
// 原理:运行时生成实现了相同接口的代理类
interface UserService { void save(); }
class UserServiceImpl implements UserService { ... }
// JDK 动态代理生成的代理类(伪代码):
class $Proxy0 implements UserService {
InvocationHandler h;
public void save() {
h.invoke(this, saveMethod, args); // 调用切面逻辑
}
}2. CGLIB(subclass-based)
java
// 不要求接口,通过继承生成目标类的子类(代理类)
// 原理:字节码增强,ASM 生成子类,重写所有方法
class UserServiceImpl {
public void save() { ... }
}
// CGLIB 生成的代理类(伪代码):
class UserServiceImpl$$EnhancerByCGLIB extends UserServiceImpl {
@Override
public void save() {
// 切面逻辑(前置通知)
super.save(); // 调用原方法
// 切面逻辑(后置通知)
}
}JDK 动态代理 vs CGLIB 对比:
| 维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 要求 | 目标类必须有接口 | 无要求(但不能代理 final 类/方法) |
| 实现方式 | 反射 + Proxy.newProxyInstance | ASM 字节码操作,生成子类 |
| 性能 | 调用时反射,略慢(JDK 8 后差距减小) | 直接调用子类方法,更快 |
| Spring 默认 | 有接口 → JDK(Spring Boot 2.x 后默认改为 CGLIB) | 无接口 → CGLIB |
| Spring Boot | spring.aop.proxy-target-class=true(默认)→ 强制用 CGLIB |
AOP 相关概念:
Aspect(切面) = 横切逻辑的模块(如日志切面、事务切面)
JoinPoint(连接点)= 可以被拦截的方法执行点
Pointcut(切入点)= 选择哪些 JoinPoint 被拦截(表达式:execution(* com..*Service.*(..)))
Advice(通知) = 在 JoinPoint 执行的动作(Before/After/Around/AfterReturning/AfterThrowing)追问与易错
追问方向:
- Boot 2.x 默认用哪个?
- CGLIB 代理 final 类会怎样?
- InvocationHandler 的三个参数?
易错点:
- ❌ 有接口就一定用 JDK——Boot 2.x 默认 CGLIB
- ❌ CGLIB 是字节码编织——是运行时生成子类
💡 记忆锚点
JDK代理 = 替身演员(必须穿同款戏服=实现接口),CGLIB = 克隆人(继承本体生成子类,但不能克隆final)。Spring Boot 2.x后默认用克隆人(CGLIB),不管你有没有戏服。