动态生成类_问:为什么 JDK 的动态代理只能使用接口?

JDK动态代理限制使用接口的原因在于Java的单继承特性,代理类默认继承Proxy并实现所有传入接口。动态生成的代理类在内存中通过字节码实现,无法处理类的多继承。在Android中,虽然JDK代理受限,但可以通过CGLIB等实现类的动态代理,利用ASM库修改子类代码来突破接口限制。
摘要由CSDN通过智能技术生成

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

上面代码运行时会在内存中默认通过字节码生成一个动态代理类,大致样子如下:

public class $Proxy1 extends Proxy implements Foo {
    ......
}

这就是为什么 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) {
        ......
    }
}

上面代码中有一个重点语句Class> cl = getProxyClass0(loader, intfs);,源码如下:

private static final WeakCache[], 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);
}

顺着上面代码我们继续看下ProxyClassFactory,如下:

private static final class ProxyClassFactoryimplements 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) {
            ......
        }
    }
}

接近真相了,我们从上面代码可以知道,代理类其实是通过ProxyGenerator生成字节码的,其生成的代理类解构大致如下:

public class $Proxy1 extends Proxy implements 传入的接口1, 传入的接口2... {
   ......
}

到现在大家都应该明白了吧,JDK 动态代理的原理是根据定义好的继承 Proxy 类规则,用传入的接口创建一个新类,这就是为什么采用动态代理时为什么只能用接口引用指向代理,而不能用传入的类引用执行动态类。

然后呢?

在安卓中你可能就此认为完事了?不是的哈,其实动态代理的核心不就是动态嘛,如果想突破接口,我们完全可以采用别的方式实现,有了字节码操作什么不能玩啊,只是 JDK 默认是为了通用,所以牺牲了一些特性而已。

在后台开发中,一种典型的实现就是基于 cglib 的动态代理,他就能突破接口限制,采用的是用创建一个继承实现类的子类,用 ASM 库动态修改子类的代码来实现的,所以可以用传入的类引用执行代理类。

到此结题,扫描右侧微信加个好友呗,朋友圈更精彩。

10e948052caf538c92b9c6775206fde7.png

小编 朋友圈技术更精彩>

28fd4bcbf19db8cf215db47fb159ea5a.gif▼往期精彩回顾▼Android 主线程崩溃与子线程崩溃有什么本质区别?你是怎么处理的?逆天!可以直接拿市场 APK 上二次开发写代码的神器!

0939cdcdb4c9ce3782fc26e76ccd77f1.png点击左下角阅读原文查看历史经典技术问题汇总,看完顺手一键三连呀~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值