JDK 的动态代理大家都熟悉,也都会用,但是你有没有深度思考一个问题,为什么 JDK 的动态代理只能使用接口? 想必有些人看到这个问法后就一脸懵逼了吧,小编下面就带大家揭密这个问题的本质。
简单而不简单的答案
之所以 JDK 的动态代理只能通过接口实现,原因是因为运行时 newProxyInstance 内部会缓存形式先通过字节码生成一个代理类,这个代理类默认已经继承了 Proxy 类,同时实现了我们传入的一堆接口;由于 Java 是单继承的,所以 JDK 动态代理只能代理接口,接口可以实现多个,但是类只能继承实现一个。
譬如我们使用动态代理样例如下:
// Foo 为接口
InvocationHandler handler = new MyInvocationHandler(...);
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class<?>[] { Foo.class }, handler);
- 1.
- 2.
- 3.
- 4.
上面代码运行时会在内存中默认通过字节码生成一个动态代理类,大致样子如下:
public class $Proxy1 extends Proxy implements Foo {
......
}
- 1.
- 2.
- 3.
这就是为什么 JDK 动态代理只能通过接口实现的原因。
深度思考 揭开面纱
分析为什么就得先从动态代理入口开始着手,即Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h),下面是其源码:
//JDK 创建动态代理
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
// 重点
final Class<?>[] intfs = interfaces.clone();
......
// 生成增强之后的动态代理 Class
Class<?> cl = getProxyClass0(loader, intfs);
// 创建增强之后的动态代理 Class 实例对象
try {
......
final Constructor<?> cons = cl.getConstructor(constructorParams);
......
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
......
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
上面代码中有一个重点语句Class cl = getProxyClass0(loader, intfs);,源码如下:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// proxyClassCache 是 Proxy 的静态变量,是 WeakCache 类,
// 里面封装了两个类 KeyFactory、ProxyClassFactory
// 重点!!! 本质调用的 ProxyClassFactory 的 apply 方法
return proxyClassCache.get(loader, interfaces);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
顺着上面代码我们继续看下ProxyClassFactory,如下:
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 所有代理类的前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 一个用来生成唯一类名的数字
private static final AtomicLong nextUniqueNumber = new AtomicLong();
// 重点,这个方法被上面的 proxyClassCache.get 调用,也就是被 WeakCache 的 get 调用
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
...... // 一堆对接口的校验逻辑,省略
String proxyPkg = null; // 代理类包名
int accessFlags = Modifier.PUBLIC | Modifier.FINAL; // flag
......
long num = nextUniqueNumber.getAndIncrement(); // 唯一类名
// 拼接的唯一全限定代理类名
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//重点!!!这里生成了增强的代理类字节码文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 调用 native 方法加载代理类字节码到内存
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
......
}
}
}
- 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.
接近真相了,我们从上面代码可以知道,代理类其实是通过ProxyGenerator生成字节码的,其生成的代理类解构大致如下:
public class $Proxy1 extends Proxy implements 传入的接口1, 传入的接口2... {
......
}
- 1.
- 2.
- 3.
到现在大家都应该明白了吧,JDK 动态代理的原理是根据定义好的继承 Proxy 类规则,用传入的接口创建一个新类,这就是为什么采用动态代理时为什么只能用接口引用指向代理,而不能用传入的类引用执行动态类。
然后呢?
在安卓中你可能就此认为完事了?不是的哈,其实动态代理的核心不就是动态嘛,如果想突破接口,我们完全可以采用别的方式实现,有了字节码操作什么不能玩啊,只是 JDK 默认是为了通用,所以牺牲了一些特性而已。
在后台开发中,一种典型的实现就是基于 cglib 的动态代理,他就能突破接口限制,采用的是用创建一个继承实现类的子类,用 ASM 库动态修改子类的代码来实现的,所以可以用传入的类引用执行代理类。
本文转载自微信公众号「码农每日一题」,可以通过以下二维码关注。转载本文请联系码农每日一题公众号。