JDK 动态代理与 CGLIB 代理详解

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 包下。

核心机制:

  1. 运行时动态生成一个代理类,该类继承自 Proxy
  2. 代理类实现目标对象的所有接口
  3. 通过 InvocationHandler 回调接口实现方法增强
  4. 底层基于 ASM 字节码框架生成代理类字节码

为什么必须有接口?

  • Java 是单继承语言,代理类已经继承了 Proxy
  • 因此只能通过实现接口的方式来定义代理的行为
  • 没有接口就无法描述代理对象能做什么

2.2 核心 API

2.2.1 Proxy 类

1
java.lang.reflect.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)

// 获取代理对象的 InvocationHandler
public static InvocationHandler getInvocationHandler(Object proxy)

2.2.2 InvocationHandler 接口

1
2
3
4
5
6
7
8
9
10
11
public interface InvocationHandler {
/**
* 代理对象每次调用方法时,都会进入这个方法
*
* @param proxy 代理对象本身(注意:不是目标对象)
* @param method 被调用的方法
* @param args 方法参数
* @return 方法返回值
*/
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
// ProxyClassFactory 是 Proxy 的内部类,负责生成代理类
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
// 1. 校验所有接口
for (Class<?> intf : interfaces) {
// 验证接口是否可被指定类加载器访问
// 验证是不是接口(不能是类)
// 验证没有重复
}

// 2. 生成代理类的全限定名
// 格式:com.sun.proxy.$ProxyN (N是自增数字)
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + "$Proxy" + num;

// 3. 生成字节码(底层调用 ProxyGenerator.generateProxyClass)
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags
);

// 4. 把字节码加载成 Class 对象
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 {

// 缓存所有 Method 对象(静态初始化时获取)
private static Method m0; // hashCode
private static Method m1; // equals
private static Method m2; // toString
private static Method m3; // addUser(接口方法)

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());
}
}

// 构造器,接收 InvocationHandler
public $Proxy0(InvocationHandler h) {
super(h);
}

// 接口方法的实现:全部调用 h.invoke()
@Override
public void addUser() {
try {
super.h.invoke(this, m3, null);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

// equals、hashCode、toString 同样走 invoke
@Override
public int hashCode() {
try {
return (Integer) super.h.invoke(this, m0, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
// ... equals / toString 同理
}

关键结论:

  • 代理类继承 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
// 1. 定义接口
public interface UserService {
void addUser(String name);
String getUserById(Long id);
}

// 2. 目标类实现接口
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;
}
}

// 3. 自定义 InvocationHandler
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;
}
}

// 4. 使用代理
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();

// getInterfaces() 返回空数组
Class<?>[] interfaces = target.getClass().getInterfaces();
System.out.println("接口数量:" + interfaces.length); // 输出 0

Object proxyObj = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
interfaces, // 空数组
(proxy, method, args1) -> {
System.out.println("【代理前置】");
return method.invoke(target, args1);
}
);

// ❌ 这里抛 ClassCastException
// 因为 $Proxy0 没有继承 OrderService,也没实现任何业务接口
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 的字节码生成库。

核心机制:

  1. 运行时动态生成目标类的子类
  2. 在子类中重写目标类的非 final 方法
  3. 方法调用时通过 MethodInterceptor 回调实现增强
  4. 底层使用 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 {
/**
* 拦截方法调用
*
* @param obj 代理对象(子类实例)
* @param method 被调用的方法
* @param args 方法参数
* @param proxy 方法代理对象,用于调用父类方法
* @return 方法返回值
*/
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

// 调用指定对象的方法(类似反射的 method.invoke)
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;

// 缓存的 Method 和 MethodProxy
private static final Method CGLIB$pay$0$Method;
private static final MethodProxy CGLIB$pay$0$Proxy;

static {
// 初始化 Method 和 MethodProxy
CGLIB$pay$0$Method = ...
CGLIB$pay$0$Proxy = ...
}

// 重写父类的 pay 方法
@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);
}
}

// CGLIB 保留的原始方法(invokeSuper 会调用这个)
final void CGLIB$pay$0(Long amount) {
super.pay(amount);
}

// final 方法不会被重写
// public final void printInfo() { ... } → 直接继承父类
}

关键结论:

  • 代理类是目标类的子类
  • 非 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
// 1. 目标类(没有实现任何接口)
public class PayService {

public void pay(Long amount) {
System.out.println("执行支付,金额:" + amount);
}

// final 方法,CGLIB 无法拦截
public final void printInfo() {
System.out.println("这是 final 方法");
}
}

// 2. 自定义方法拦截器
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();

// 调用父类方法(注意用 invokeSuper,不是 invoke)
Object result = proxy.invokeSuper(obj, args);

// 后置增强
long cost = System.currentTimeMillis() - start;
System.out.println("【CGLIB后置】方法 " + method.getName() + " 耗时:" + cost + "ms");

return result;
}
}

// 3. 使用 CGLIB 创建代理
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 方法 ---");
// 调用 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 {

/**
* 创建 CGLIB 代理
*
* @param targetClass 目标类的 Class 对象
* @param interceptor 方法拦截器
* @return 代理对象
*/
@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; // 走第 0 个拦截器(日志增强)
}
return 1; // 走第 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
# 强制使用 CGLIB 代理(即使有接口也用 CGLIB)
spring.aop.proxy-target-class=true

# 默认值为 false,即有接口时用 JDK 代理
# spring.aop.proxy-target-class=false

XML 配置

1
2
{/* 开启自动代理,默认 false 即优先 JDK 代理 */}
<aop:aspectj-autoproxy proxy-target-class="true"/>

Java Config

1
2
3
4
5
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制 CGLIB
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(); // ❌ 这里调用的是原始对象的 methodB,不走代理!
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// ...
}
}

原因:this 是原始对象,不是代理对象,所以 methodB 上的事务注解不生效。

解决方案:

  1. 注入自己:@Autowired private OrderService self; 然后 self.methodB()
  2. 使用 AopContext.currentProxy() 获取代理对象
  3. 拆分到不同的类中

六、其他动态代理技术简介

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
// ByteBuddy 简单示例
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) 进行反射调用,每次调用都需要:

  1. 方法权限检查
  2. 参数装箱拆箱
  3. 反射调用开销

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(原始对象),不走代理,所以注解不生效。

解决方法:

  1. 注入自己的代理对象
  2. 使用 AopContext.currentProxy()
  3. 拆分到不同类

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