JDK 动态代理与 CGLIB 代理详解
本文档系统梳理 JDK 动态代理与 CGLIB 代理的底层原理、源码实现、使用场景、常见坑点及横向对比。
目录
一、动态代理概述
1.1 什么是动态代理
动态代理是一种在运行时动态生成代理类的设计模式,代理对象可以在不修改原始类代码的前提下,对目标方法进行增强(前置处理、后置处理、异常处理等)。
1.2 为什么需要动态代理
- 解耦:业务逻辑与横切关注点(日志、事务、权限、监控)分离
- 无侵入:不需要修改原始类代码
- 灵活:运行时动态生成,编译期不需要额外文件
- AOP 基础:Spring AOP、MyBatis Mapper、Dubbo 等框架的底层基石
1.3 主流实现分类
| 类型 |
代表技术 |
核心特点 |
| 基于接口 |
JDK 动态代理 |
目标类必须实现接口 |
| 基于继承 |
CGLIB、ByteBuddy |
不需要接口,生成目标类子类 |
| 基于字节码 |
ASM、Javassist |
直接操作字节码,底层框架使用 |
二、JDK 动态代理
2.1 核心原理
JDK 动态代理是 JDK 原生提供的代理机制,位于 java.lang.reflect 包下。
核心机制:
- 运行时动态生成一个代理类,该类继承自
Proxy
- 代理类实现目标对象的所有接口
- 通过
InvocationHandler 回调接口实现方法增强
- 底层基于 ASM 字节码框架生成代理类字节码
为什么必须有接口?
- Java 是单继承语言,代理类已经继承了
Proxy
- 因此只能通过实现接口的方式来定义代理的行为
- 没有接口就无法描述代理对象能做什么
2.2 核心 API
2.2.1 Proxy 类
核心方法:
1 2 3 4 5 6 7 8 9 10 11 12
| public static Object newProxyInstance( ClassLoader loader, // 类加载器,通常用目标类的类加载器 Class<?>[] interfaces, // 目标类实现的接口数组 InvocationHandler h // 调用处理器,增强逻辑写在这里 )
public static boolean isProxyClass(Class<?> cl)
public static InvocationHandler getInvocationHandler(Object proxy)
|
2.2.2 InvocationHandler 接口
1 2 3 4 5 6 7 8 9 10 11
| public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
|
2.3 源码分析
2.3.1 newProxyInstance 执行流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| newProxyInstance(loader, interfaces, h) │ ├─ 1. 安全检查:校验接口访问权限 │ ├─ 2. 获取/生成代理类 Class 对象 │ └─ getProxyClass0(loader, interfaces) │ └─ 缓存中存在?→ 直接返回 │ └─ 不存在 → ProxyClassFactory 生成 │ ├─ 3. 获取代理类的构造器(参数为 InvocationHandler) │ └─ cons = cl.getConstructor(InvocationHandler.class) │ └─ 4. 反射创建代理实例 └─ cons.newInstance(new Object[]{h})
|
2.3.2 代理类生成关键代码(简化版)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { for (Class<?> intf : interfaces) { }
long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + "$Proxy" + num;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags );
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } }
|
2.3.3 生成的代理类长什么样?
生成的 $Proxy0 类(反编译后)大致结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public final class $Proxy0 extends Proxy implements UserService {
private static Method m0; private static Method m1; private static Method m2; private static Method m3;
static { try { m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("com.example.UserService").getMethod("addUser"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } }
public $Proxy0(InvocationHandler h) { super(h); }
@Override public void addUser() { try { super.h.invoke(this, m3, null); } catch (Error | RuntimeException e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } }
@Override public int hashCode() { try { return (Integer) super.h.invoke(this, m0, null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }
|
关键结论:
- 代理类继承
Proxy,实现所有目标接口
- 每个接口方法都对应一个静态
Method 对象
- 方法调用全部转发给
InvocationHandler.invoke()
- 受检异常会被包装成
UndeclaredThrowableException
2.4 使用案例
2.4.1 基础示例:日志增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public interface UserService { void addUser(String name); String getUserById(Long id); }
public class UserServiceImpl implements UserService { @Override public void addUser(String name) { System.out.println("添加用户:" + name); }
@Override public String getUserById(Long id) { System.out.println("查询用户:" + id); return "User-" + id; } }
public class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("【日志前置】调用方法:" + method.getName()); long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long cost = System.currentTimeMillis() - start; System.out.println("【日志后置】方法 " + method.getName() + " 执行耗时:" + cost + "ms");
return result; } }
public class JdkProxyDemo { public static void main(String[] args) { UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogInvocationHandler(target) );
proxy.addUser("张三"); String user = proxy.getUserById(1001L); System.out.println("返回结果:" + user); } }
|
输出结果:
1 2 3 4 5 6 7
| 【日志前置】调用方法:addUser 添加用户:张三 【日志后置】方法 addUser 执行耗时:0ms 【日志前置】调用方法:getUserById 查询用户:1001 【日志后置】方法 getUserById 执行耗时:0ms 返回结果:User-1001
|
2.4.2 通用代理工厂(封装)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class JdkProxyFactory {
@SuppressWarnings("unchecked") public static <T> T createProxy(T target, Runnable before, Runnable after) { return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> { before.run(); Object result = method.invoke(target, args); after.run(); return result; } ); }
public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = JdkProxyFactory.createProxy( target, () -> System.out.println("=== before ==="), () -> System.out.println("=== after ===") ); proxy.addUser("test"); } }
|
2.4.3 错误示例:无接口强行代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
public class OrderService { public void createOrder() { System.out.println("执行创建订单"); } }
public class JdkProxyFailDemo { public static void main(String[] args) { OrderService target = new OrderService();
Class<?>[] interfaces = target.getClass().getInterfaces(); System.out.println("接口数量:" + interfaces.length);
Object proxyObj = Proxy.newProxyInstance( target.getClass().getClassLoader(), interfaces, (proxy, method, args1) -> { System.out.println("【代理前置】"); return method.invoke(target, args1); } );
OrderService proxy = (OrderService) proxyObj; proxy.createOrder(); } }
|
报错信息:
1 2
| Exception in thread "main" java.lang.ClassCastException: class jdk.proxy1.$Proxy0 cannot be cast to class com.example.OrderService
|
2.5 常见坑点
| 坑点 |
说明 |
解决方案 |
| 目标类无接口 |
强转时报 ClassCastException |
改用 CGLIB / 抽取接口 |
| 强转成实现类 |
即使有接口,转成实现类也报错 |
必须转成接口类型 |
| invoke 中调用 proxy |
在 invoke 里调用 proxy 的方法会导致死循环递归 |
用 target 调用真实方法 |
| 受检异常包装 |
接口没声明的受检异常会被包成 UndeclaredThrowableException |
在 invoke 中捕获处理,或在接口声明 |
| equals/hashCode/toString |
这三个方法也会走 invoke 逻辑 |
在 invoke 中判断 method 名称做特殊处理 |
| 性能 |
首次生成代理类较慢,反射调用有开销 |
频繁调用场景可考虑 CGLIB/ByteBuddy |
三、CGLIB 代理
3.1 核心原理
CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库。
核心机制:
- 运行时动态生成目标类的子类
- 在子类中重写目标类的非 final 方法
- 方法调用时通过
MethodInterceptor 回调实现增强
- 底层使用 ASM 直接生成字节码
为什么不需要接口?
- CGLIB 采用继承的方式,代理类是目标类的子类
- 子类天然拥有父类的所有非私有、非 final 方法
- 因此不需要目标类实现任何接口
注意:CGLIB 项目已停止维护,新项目推荐使用 ByteBuddy 替代。
3.2 核心 API
3.2.1 Enhancer 类
CGLIB 的核心类,相当于 JDK 代理中的 Proxy。
1
| net.sf.cglib.proxy.Enhancer
|
常用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void setSuperclass(Class superclass)
public void setCallback(Callback callback)
public void setCallbacks(Callback[] callbacks)
public Object create()
public Object create(Class[] argumentTypes, Object[] arguments)
|
3.2.2 MethodInterceptor 接口
1 2 3 4 5 6 7 8 9 10 11 12
| public interface MethodInterceptor extends Callback {
Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable; }
|
3.2.3 MethodProxy 类
1
| net.sf.cglib.proxy.MethodProxy
|
1 2 3 4 5
| public Object invokeSuper(Object obj, Object[] args) throws Throwable
public Object invoke(Object obj, Object[] args) throws Throwable
|
3.3 源码分析
3.3.1 Enhancer.create() 执行流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Enhancer.create() │ ├─ 1. 校验:父类不能是 final │ ├─ 2. 生成代理类名 │ └─ 格式:父类名$$EnhancerByCGLIB$$随机哈希 │ 例如:PayService$$EnhancerByCGLIB$$1a2b3c4d │ ├─ 3. 使用 DefaultGeneratorStrategy 生成字节码 │ └─ 底层通过 ASM 写字节码 │ ├─ 生成类:继承目标类 │ ├─ 为每个非 final 方法生成重写版本 │ ├─ 每个方法对应一个 CGLIB$xxx 原始方法引用 │ └─ 方法内部调用 intercept() │ ├─ 4. 加载字节码 → Class 对象 │ └─ 5. 反射创建实例,设置 Callback
|
3.3.2 生成的代理类结构(简化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class PayService$$EnhancerByCGLIB$$1a2b3c4d extends PayService {
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$pay$0$Method; private static final MethodProxy CGLIB$pay$0$Proxy;
static { CGLIB$pay$0$Method = ... CGLIB$pay$0$Proxy = ... }
@Override public void pay(Long amount) { MethodInterceptor interceptor = this.CGLIB$CALLBACK_0; if (interceptor != null) { interceptor.intercept(this, CGLIB$pay$0$Method, new Object[]{amount}, CGLIB$pay$0$Proxy); } else { super.pay(amount); } }
final void CGLIB$pay$0(Long amount) { super.pay(amount); }
}
|
关键结论:
- 代理类是目标类的子类
- 非 final 方法被重写,内部调用
MethodInterceptor.intercept()
- final 方法无法被重写,直接继承父类实现,不会走拦截逻辑
MethodProxy.invokeSuper() 比反射 method.invoke() 性能更好
3.4 使用案例
3.4.1 基础示例:无接口类的代理
1 2 3 4 5 6
| {/* Maven 依赖 */} <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public class PayService {
public void pay(Long amount) { System.out.println("执行支付,金额:" + amount); }
public final void printInfo() { System.out.println("这是 final 方法"); } }
public class LogMethodInterceptor implements MethodInterceptor {
@Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("【CGLIB前置】调用方法:" + method.getName()); long start = System.currentTimeMillis();
Object result = proxy.invokeSuper(obj, args);
long cost = System.currentTimeMillis() - start; System.out.println("【CGLIB后置】方法 " + method.getName() + " 耗时:" + cost + "ms");
return result; } }
public class CglibProxyDemo { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PayService.class); enhancer.setCallback(new LogMethodInterceptor());
PayService proxy = (PayService) enhancer.create();
proxy.pay(1000L);
System.out.println("\n--- 调用 final 方法 ---"); proxy.printInfo(); } }
|
输出结果:
1 2 3 4 5 6
| 【CGLIB前置】调用方法:pay 执行支付,金额:1000 【CGLIB后置】方法 pay 耗时:0ms
--- 调用 final 方法 --- 这是 final 方法
|
3.4.2 CGLIB 代理工厂(通用封装)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class CglibProxyFactory {
@SuppressWarnings("unchecked") public static <T> T createProxy(Class<T> targetClass, MethodInterceptor interceptor) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetClass); enhancer.setCallback(interceptor); return (T) enhancer.create(); }
public static void main(String[] args) { PayService proxy = CglibProxyFactory.createProxy( PayService.class, (obj, method, args1, proxy1) -> { System.out.println("=== before ==="); Object result = proxy1.invokeSuper(obj, args1); System.out.println("=== after ==="); return result; } ); proxy.pay(500L); } }
|
3.4.3 回调过滤器:不同方法走不同拦截逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class CallbackFilterDemo { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PayService.class);
Callback logInterceptor = new LogMethodInterceptor(); Callback noOpInterceptor = NoOp.INSTANCE;
enhancer.setCallbacks(new Callback[]{logInterceptor, noOpInterceptor});
enhancer.setCallbackFilter(method -> { if ("pay".equals(method.getName())) { return 0; } return 1; });
PayService proxy = (PayService) enhancer.create(); proxy.pay(100L); proxy.printInfo(); } }
|
3.5 常见坑点
| 坑点 |
说明 |
解决方案 |
| final 类无法代理 |
CGLIB 生成子类,final 类不能被继承 |
改用 JDK 代理 / 去掉 final |
| final 方法无法拦截 |
final 方法不能被重写,增强失效 |
去掉 final / 换 JDK 代理(接口方法) |
| private 方法无法代理 |
子类无法访问父类私有方法 |
改为 protected / public |
| 构造器调用两次 |
CGLIB 创建代理时可能触发两次构造 |
构造器中不要有副作用逻辑 |
| 用错 invoke 方法 |
用 method.invoke(target, args) 会导致循环 |
必须用 methodProxy.invokeSuper(obj, args) |
| CGLIB 已停止维护 |
官方不再更新,不兼容某些新特性 |
新项目考虑 ByteBuddy |
四、横向对比
4.1 核心对比表
| 对比维度 |
JDK 动态代理 |
CGLIB 代理 |
| 底层实现 |
JDK 原生,基于 ASM 生成字节码 |
第三方库,基于 ASM 生成字节码 |
| 代理方式 |
实现目标接口 |
继承目标类(生成子类) |
| 是否需要接口 |
✅ 必须实现至少一个接口 |
❌ 不需要接口 |
| 是否需要依赖 |
❌ JDK 自带,无需额外依赖 |
✅ 需要引入 cglib 包 |
| final 类 |
不涉及(只代理接口) |
❌ 无法代理 |
| final 方法 |
不涉及(接口方法不能是 final) |
❌ 无法拦截增强 |
| private 方法 |
不涉及(接口方法都是 public) |
❌ 无法代理 |
| 代理对象类型 |
接口类型 |
目标类类型(子类) |
| 核心回调接口 |
InvocationHandler |
MethodInterceptor |
| 调用原方法方式 |
method.invoke(target, args)(反射) |
methodProxy.invokeSuper(obj, args)(更快) |
| 性能(首次) |
较慢(生成代理类) |
较慢(生成子类) |
| 性能(运行时) |
反射调用,中等 |
方法代理调用,略快于 JDK |
| Spring AOP 默认 |
目标有接口时默认使用 |
目标无接口时使用 |
| 代表框架 |
MyBatis Mapper、Spring AOP(接口模式) |
Spring AOP(类模式)、旧版 Hibernate |
4.2 性能对比
1 2 3 4 5 6 7
| 性能排序(方法调用速度): 直接调用 > CGLIB (MethodProxy) > JDK 动态代理 > 纯反射
说明: - CGLIB 使用 MethodProxy.invokeSuper() 比 JDK 的反射调用快约 10%~30% - 但首次生成代理类的开销两者差不多,都需要运行时生成字节码 - 现代 JVM 的反射优化已经很好,实际差距越来越小
|
4.3 选型建议
| 场景 |
推荐方案 |
理由 |
| 目标类有接口 |
JDK 动态代理 |
JDK 原生、无依赖、更轻量 |
| 目标类无接口 |
CGLIB / ByteBuddy |
只能用继承方式 |
| Spring Boot 项目 |
用 Spring AOP 即可 |
框架自动选择,不用关心底层 |
| 对性能要求极高 |
ByteBuddy / ASM |
现代字节码工具,性能更好 |
| 需要支持 final 类 |
只能用 JDK 代理(接口) |
CGLIB 无法代理 final 类 |
| 新项目框架选型 |
优先考虑 ByteBuddy |
CGLIB 已停止维护 |
五、Spring AOP 中的代理选择
5.1 选择逻辑
Spring AOP 会根据目标对象是否实现接口,自动选择代理方式:
1 2 3 4 5 6
| 目标对象是否实现接口? │ ├─ 是 → 默认使用 JDK 动态代理 │ (可通过配置强制使用 CGLIB) │ └─ 否 → 使用 CGLIB 代理
|
5.2 配置方式
Spring Boot 配置
1 2 3 4 5
| spring.aop.proxy-target-class=true
|
XML 配置
1 2
| {/* 开启自动代理,默认 false 即优先 JDK 代理 */} <aop:aspectj-autoproxy proxy-target-class="true"/>
|
Java Config
1 2 3 4 5
| @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class AppConfig { }
|
5.3 Spring Boot 2.x / 3.x 变化
- Spring Boot 1.x:
spring.aop.proxy-target-class 默认 false
- Spring Boot 2.x / 3.x:
spring.aop.proxy-target-class 默认 true
- 即默认全部使用 CGLIB 代理
- 原因:减少因代理类型不同导致的注入问题(注入实现类 vs 注入接口)
5.4 Spring 事务代理失效场景
面试高频:为什么 Spring 事务有时候不生效?
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Service public class OrderService {
@Transactional public void methodA() { this.methodB(); }
@Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() { } }
|
原因:this 是原始对象,不是代理对象,所以 methodB 上的事务注解不生效。
解决方案:
- 注入自己:
@Autowired private OrderService self; 然后 self.methodB()
- 使用
AopContext.currentProxy() 获取代理对象
- 拆分到不同的类中
六、其他动态代理技术简介
6.1 ByteBuddy(现代首选)
- 定位:新一代字节码生成与操作库
- 特点:
- 对 ASM 做了高层封装,API 优雅、类型安全
- 性能接近原生 ASM
- 支持 Java 8~21+ 新特性(Record、Sealed Class 等)
- CGLIB 的替代者,CGLIB 已停止维护
- 使用场景:Spring Boot 3.x、Mockito、Jackson、Netty 等
1 2 3 4 5 6 7 8
| new ByteBuddy() .subclass(PayService.class) .method(ElementMatchers.named("pay")) .intercept(MethodDelegation.to(LogInterceptor.class)) .make() .load(PayService.class.getClassLoader()) .getLoaded();
|
6.2 Javassist
- 定位:JBoss 出品的字节码编辑库
- 特点:
- 支持用 Java 源码字符串生成类,不需要懂字节码指令
- 学习成本低
- 性能略逊于 ASM / ByteBuddy
- 使用场景:旧版 Dubbo、Hibernate、JBoss 等
6.3 ASM
- 定位:最底层的字节码操作框架
- 特点:
- 直接读写 class 文件二进制
- 性能天花板
- 学习成本极高,需要懂 JVM 字节码指令
- 使用场景:几乎所有字节码工具的底层(JDK Proxy、CGLIB、ByteBuddy 都基于 ASM)
6.4 技术关系图
1 2 3 4 5 6 7 8 9 10 11 12 13
| ┌─────────────────────────────────────┐ │ 业务层动态代理 │ │ (JDK Proxy / CGLIB / 框架封装) │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ 字节码生成工具层 │ │ (ByteBuddy / Javassist / ASM) │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ JVM 字节码 │ └─────────────────────────────────────┘
|
七、面试高频问答
Q1:JDK 动态代理为什么必须要有接口?
因为 JDK 动态代理生成的代理类已经继承了 Proxy 类,Java 是单继承语言,所以只能通过实现接口的方式来定义代理对象的行为。没有接口就无法描述代理对象能做什么。
Q2:CGLIB 能代理接口吗?
可以。CGLIB 的 Enhancer 也支持 setInterfaces() 方法,让代理类同时实现指定接口。但这不是 CGLIB 的主要使用场景,有接口时直接用 JDK 代理更轻量。
Q3:JDK 动态代理和 CGLIB 哪个性能更好?
- 首次生成:两者差不多,都需要运行时生成字节码
- 运行时调用:CGLIB 的
MethodProxy.invokeSuper() 比 JDK 的反射调用略快(约 10%~30%)
- 现代 JVM:反射优化越来越好,实际差距在缩小
- 绝大多数业务场景下,性能差异可以忽略,优先考虑适用性
Q4:CGLIB 为什么比 JDK 动态代理快?
JDK 动态代理使用 method.invoke(target, args) 进行反射调用,每次调用都需要:
- 方法权限检查
- 参数装箱拆箱
- 反射调用开销
CGLIB 的 MethodProxy.invokeSuper() 直接生成了调用父类方法的字节码,避免了反射的开销,相当于直接调用。
Q5:Spring AOP 默认用哪种代理?
- Spring Boot 2.x / 3.x:默认
spring.aop.proxy-target-class=true,即默认全部使用 CGLIB
- 传统 Spring:目标有接口时默认 JDK 代理,无接口时用 CGLIB
- 可以通过
proxyTargetClass=true 强制使用 CGLIB
Q6:同一个类中方法调用,为什么事务/缓存注解不生效?
因为 Spring AOP 是基于代理的,只有通过代理对象调用的方法才会走增强逻辑。同类内部调用用的是 this(原始对象),不走代理,所以注解不生效。
解决方法:
- 注入自己的代理对象
- 使用
AopContext.currentProxy()
- 拆分到不同类
Q7:CGLIB 和 ByteBuddy 怎么选?
- 新项目:优先选 ByteBuddy,CGLIB 已经停止维护
- 老项目:如果已经用了 CGLIB 且没问题,可以继续用
- Spring Boot 3.x:内部已经在逐步替换为 ByteBuddy
- ByteBuddy 的 API 更现代、更安全,功能也更强大
Q8:动态代理和静态代理有什么区别?
| 对比维度 |
静态代理 |
动态代理 |
| 生成时机 |
编译期 |
运行期 |
| 代理类 |
手写或编译生成 |
运行时动态生成 |
| 灵活性 |
低,每个目标类都要写代理 |
高,一个通用代理可以代理多个类 |
| 代码量 |
多,重复代码多 |
少,通用逻辑复用 |
| 性能 |
高(直接调用) |
略低(反射/字节码) |
| 代表 |
装饰器模式、AspectJ 编译期织入 |
JDK Proxy、CGLIB |
文档版本:v1.0
最后更新:2026-08
适用范围:Java 8+ / Spring Boot 2.x / 3.x