为什么 JDK 的动态代理只能使用接口?

服务器
JDK 的动态代理大家都熟悉,也都会用,但是你有没有深度思考一个问题,为什么 JDK 的动态代理只能使用接口? 想必有些人看到这个问法后就一脸懵逼了吧,小编下面就带大家揭密这个问题的本质。

[[360690]]

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 库动态修改子类的代码来实现的,所以可以用传入的类引用执行代理类。

 本文转载自微信公众号「码农每日一题」,可以通过以下二维码关注。转载本文请联系码农每日一题公众号。

 

责任编辑:武晓燕 来源: 码农每日一题
相关推荐

2023-07-05 08:17:38

JDK动态代理接口

2022-02-22 22:44:46

接口源码对象

2015-09-22 11:09:47

Java 8动态代理

2023-12-06 08:23:44

代理模式设计模式

2021-04-22 09:58:15

JDK代理动态

2022-09-01 10:40:29

SpringAOPJDK

2022-03-20 18:29:38

爬虫反爬虫代理

2021-03-16 21:42:37

反向代理正向代理

2024-02-04 16:51:47

2021-07-03 08:59:49

动态代理JDK

2022-06-30 10:05:30

Java接口动态代理

2021-01-29 14:14:47

动态代理缓存

2010-02-26 15:51:02

Linux用户

2023-04-06 00:11:12

Java接口开发

2025-02-27 00:32:35

2023-12-05 09:14:54

2022-07-05 14:19:30

Spring接口CGLIB

2022-11-15 09:57:51

Java接口

2021-12-09 07:54:19

IDL字段兼容

2019-07-01 16:02:32

5G路由器网络
点赞
收藏
wot

51CTO技术栈公众号