从字节码角度探究 JDK 和 CGLIB 动态代理区别

提到 Java 中的 JDK 动态代理和 CGLIB 动态代理的区别,网上找到的通常是如下介绍:

JDK 动态代理利用拦截器和反射来实现,不需要第三方库支持,只需要 JDK 环境就可以进行代理。CGLIB 动态代理利用 ASM 开源包,加载代理对象类的 Class 文件,通过修改其底层字节码生成新的子类来实现代理。

JDK 动态代理只能针对接口而不能针对类实现代理。CGLIB 既能通过接口代理又能通过类代理。

上面描述的都是结论,但为什么 JDK 动态代理只能针对接口,而 CGLIB 可以针对类代理?本篇文章将从两者生成的 Class 字节码文件角度一探究竟。

准备工作

先创建一个简单的 UserDao 类,以及它的实现类:

1
2
3
4
5
6
package com.zhangzw.dao;

public interface UserDao {
/** 修改用户名 */
void updateName();
}
1
2
3
4
5
6
7
8
9
10
package com.zhangzw.dao.impl;

import com.zhangzw.dao.UserDao;

public class UserDaoImpl implements UserDao {
@Override
public void updateName() {
System.out.println("do updateName.");
}
}

JDK 动态代理

使用 JDK 的动态代理,通常调用的是 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法,该方法直接生成了代理对象。为了拿到代理类 Class 的二进制内容,我们跟进代码内部,发现调用了 byte[] ProxyGenerator.generateProxyClass(String var0, Class<?>[] var1) 方法,该方法返回 Class 的 byte 数组,然后将该 byte 数组保存到文件中。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.zhangzw;

import java.io.File;
import java.io.FileOutputStream;
import org.junit.Test;
import com.zhangzw.dao.UserDao;
import sun.misc.ProxyGenerator;

public class AopTest {

@Test
public void jdkProxy() throws Exception {
byte[] bytes = ProxyGenerator.generateProxyClass("JDKUserDao", new Class[]{UserDao.class});
FileOutputStream outputStream = new FileOutputStream(new File("D:\\JDKUserDao.class"));
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
}

}

执行如上单元测试,生成 JDKUserDao.class 文件,再将该文件放到 IDEA 中,反编译查看文件内容:

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
66
67
68
69
import com.zhangzw.dao.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class JDKUserDao extends Proxy implements UserDao {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public JDKUserDao(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String) super.h.invoke(this, m2, (Object[]) null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final void updateName() throws {
try {
super.h.invoke(this, m3, (Object[]) null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer) super.h.invoke(this, m0, (Object[]) null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
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.zhangzw.dao.UserDao").getMethod("updateName");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

可以看出,生成的 JDKUserDao 类继承了 Proxy 类,并实现了代理的 UserDao 接口。在 JDKUserDao 类中可以看到实现了 UserDao 的 updateName 方法,内部调用了构造方法传入的 InvocationHandler 的 invoke 方法,也就是将 updateName 方法的具体逻辑提到 InvocationHandler 的 invoke 方法中了。

类结构图如下:

1579504493604

由于这种结构的关系,这也就说明了代理类 JDKUserDao 其实是一个 Proxy,因为 Java 中的单继承,所以 JDK 动态代理不能实现针对类的代理,但可以对多个接口实现代理。

CGLIB 动态代理

针对 CGLIB 的分析也是一样的,就是要拿到生成的 Class 文件。

首先引入 cglib 依赖包:

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>

执行如下单元测试,生成 Class 文件:

需要注意的是:为了让生成的 Class 落地到磁盘,需要加上 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY 属性。

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
package com.zhangzw;

import org.junit.Test;

import com.zhangzw.dao.UserDao;
import com.zhangzw.dao.impl.UserDaoImpl;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

public class AopTest {

@Test
public void cgLibProxy() {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\cglib"); // Class 文件保存路径

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserDao.class); // 代理接口
// enhancer.setSuperclass(UserDaoImpl.class); // 代理类
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("Before");
// methodProxy.invokeSuper(o, objects); // 执行被代理类的目标方法,如果被代理的是接口则不能执行该方法。
System.out.println("After");
return null;
});
Object userDao = enhancer.create();
}
}

反编译查看生成的 Class:

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
package com.zhangzw.dao;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class UserDao$$EnhancerByCGLIB$$a4758831 implements UserDao, Factory {

// ...

public final void updateName() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if (var10000 != null) {
var10000.intercept(this, CGLIB$updateName$4$Method, CGLIB$emptyArgs, CGLIB$updateName$4$Proxy);
} else {
super.updateName();
}
}

// ...
}

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
package com.zhangzw.dao.impl;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class UserDaoImpl$$EnhancerByCGLIB$$4a4f3531 extends UserDaoImpl implements Factory {

// ...

public final void updateName() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if (var10000 != null) {
var10000.intercept(this, CGLIB$updateName$0$Method, CGLIB$emptyArgs, CGLIB$updateName$0$Proxy);
} else {
super.updateName();
}
}

// ...
}

CGLIB 生成的代理类里面内容比较多,删掉了一些不必太关注的代码。

从类结构上可以看出,针对接口的代理就是对 UserDao 创建了一个实现类,针对类的代理就是创建了一个 UserDaoImpl 类的子类。所以这就是为什么 CGLIB 可以实现针对类的代理的原因。直接生成子类,没有像 JDK 动态代理那样绕来绕去。

由于生成的代理类是被代理类的子类,所以被代理类的方法不能是 final 的,否则生成的代理类不会重写被代理类的方法,就不会执行 MethodInterceptor.intercept 方法。

总结

对技术细节,不仅要知其然,还要知其所以然。否则只是记住结论,既不容易理解又容易遗忘。