什么是JDK com.sun.proxy.$Proxy类?

2025/03/16

1. 概述

当我们使用动态代理时,JDK会动态生成一个$Proxy类。通常,这个$Proxy类的全限定类名有点类似于com.sun.proxy.$Proxy0。正如Java文档所述,“$Proxy”是代理类的保留名称前缀。

在本教程中,我们将探索这个$Proxy类。

2. $Proxy类

在开始之前,我们先来区分一下java.lang.reflect.Proxy类和$Proxy类,java.lang.reflect.Proxy是JDK内置类,而$Proxy类则是在运行时动态生成的。从类层次结构来看,$Proxy类继承自java.lang.reflect.Proxy类。

2.1 动态代理示例

为了有讨论的基础,我们定义两个接口:BasicOperation和AdvancedOperation。BasicOperation接口包含add和subtract方法:

public interface BasicOperation {
    int add(int a, int b);

    int subtract(int a, int b);
}

并且,AdvancedOperation接口具有multiply和divide方法:

public interface AdvancedOperation {
    int multiply(int a, int b);

    int divide(int a, int b);
}

为了获取新生成的代理类-$Proxy类,我们可以调用Proxy::getProxyClass方法:

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?>[] interfaces = {BasicOperation.class, AdvancedOperation.class};
Class<?> proxyClass = Proxy.getProxyClass(classLoader, interfaces);

但是上述proxyClass只存在于正在运行的JVM中,我们无法直接查看其类成员。

2.2 转储$Proxy类

为了仔细检查这个$Proxy类,我们最好将其转储到磁盘。使用Java 8时,我们可以在命令行上指定“sun.misc.ProxyGenerator.saveGeneratedFiles”选项:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

或者我们可以通过调用System::setProperty方法来设置此选项:

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

在Java 9及更高版本中,我们应该改用“jdk.proxy.ProxyGenerator.saveGeneratedFiles”选项。为什么会有这样的区别?因为Java模块系统,ProxyGenerator类的包发生了变化。在Java 8中,ProxyGenerator位于“sun.misc”包中;但是,从Java 9开始,ProxyGenerator已移至“java.lang.reflect”包中。

如果我们仍然不知道哪个选项合适,我们可以查找ProxyGenerator类的saveGeneratedFiles字段来确定正确的选项。

这里要注意:ProxyGenerator类只读取该属性一次,这意味着在JVM显式或隐式生成任何$Proxy类后,System::setProperty方法将不起作用。具体来说,调用Proxy::getProxyClass或Proxy::newProxyInstance方法将显式生成$Proxy类。另一方面,当我们读取注解时,特别是在单元测试框架内,JVM将隐式或自动生成$Proxy类来表示注解实例。

转储的类文件的具体位置与其完全限定类名直接相关,例如,如果新生成的类名为“com.sun.proxy.$Proxy0”,则转储的类文件将在当前目录中为“com/sun/proxy/$Proxy0.class”:

2.3 $Proxy类的成员

现在,是时候检查这个生成的$Proxy类的类成员了。

我们先来看一下类的层次结构,$Proxy0类的超类是java.lang.reflect.Proxy,这隐含地解释了为什么动态代理只支持接口。此外,$Proxy0类实现了我们之前定义的BasicOperation和AdvancedOperation接口:

public final class $Proxy0 extends Proxy implements BasicOperation, AdvancedOperation

为了便于阅读,我们将$Proxy0类的字段名称更改为更有意义的名称。hashCodeMethod、equalsMethod和toStringMethod字段可追溯到Object类;addMethod和subtractMethod字段与BasicOperation接口相关;multiplyMethod和divideMethod字段映射到AdvanceOperation接口:

private static Method hashCodeMethod;
private static Method equalsMethod;
private static Method toStringMethod;
private static Method addMethod;
private static Method subtractMethod;
private static Method multiplyMethod;
private static Method divideMethod;

最后,$Proxy0类中定义的方法遵循相同的逻辑:它们的所有实现都委托给InvocationHandler::invoke方法。并且,$Proxy0类将从其构造函数中获取一个InvocationHandler实例:

public $Proxy0(InvocationHandler handler) {
    super(handler);
}

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

3. 代理如何工作

了解完$Proxy类本身之后,我们再进一步了解一下$Proxy类是如何生成的,又是如何加载$Proxy类的。关键的逻辑就在java.lang.reflect.Proxy和ProxyGenerator这两个类中。

随着新Java版本的发布,Proxy和ProxyGenerator类的实现细节也在不断发展。粗略地说,ProxyGenerator负责生成$Proxy类的字节数组,而Proxy类负责将此字节数组加载到JVM中。

现在,我们使用Java 8、Java 11和Java 17进行讨论,因为它们是LTS(长期支持)版本。

3.1 Java 8

在Java 8中,我们可以用五个步骤来描述$Proxy类的生成过程:

Proxy::getProxyClass或Proxy::newProxyInstance方法是我们的起点-两者都会调用Proxy::getProxyClass0方法。而且,Proxy::getProxyClass0方法是私有方法,会进一步调用ProxyClassFactory::apply方法。

ProxyClassFactory是Proxy类中定义的静态嵌套类,并且,它的apply方法确定即将创建的类的包名称、类名称和访问标志。然后,apply方法将调用ProxyGenerator::generateProxyClass方法。

在Java 8中,ProxyGenerator类是“sun.misc”包中定义的公共类。自Java 9以来,它已迁移到“java.lang.reflect”包。并且,generateProxyClass方法将创建一个ProxyGenerator实例,调用其generateClassFile方法(其职责是生成字节码),可选地转储类文件,并返回生成的字节数组。

字节码生成成功后,Proxy::defineClass0方法负责将该字节数组加载到正在运行的JVM中。最终,我们得到一个动态生成的$Proxy类。

3.2 Java 11

与Java 8版本相比,Java 11引入了三个主要变化:

  • Proxy类添加了一个新的getProxyConstructor方法和一个静态嵌套的ProxyBuilder类
  • 对于Java模块系统,ProxyGenerator已迁移到“java.lang.reflect”包并成为包私有类
  • 为了将生成的字节数组加载到JVM中,Unsafe::defineClass开始发挥作用

3.3 Java 17

与Java 11版本相比,Java 17主要有两点变化:

  • 从实现角度来看,ProxyGenerator类利用JDK内置的ASM进行字节码生成
  • JavaLangAccess::defineClass方法负责将生成的字节码加载到JVM中

4. 使用代理注解

在Java中,注解类型是一种特殊的接口类型。但是,我们可能想知道如何创建注解实例。其实,我们不需要。当我们使用Java反射API读取注解时,JVM会动态生成一个$Proxy类作为注解类型的实现:

FunctionalInterface instance = Consumer.class.getDeclaredAnnotation(FunctionalInterface.class);
Class<?> clazz = instance.getClass();

boolean isProxyClass = Proxy.isProxyClass(clazz);
assertTrue(isProxyClass);

在上面的代码片段中,我们使用Consumer类来获取其FunctionalInterface实例,然后获取该实例的类,最后使用Proxy::isProxyClass方法检查该类是否是$Proxy类。

5. 总结

在本教程中,我们首先介绍了一个动态代理示例,然后转储了生成的$Proxy类并检查了其成员。更进一步,我们解释了Proxy和ProxyGenerator类如何协同工作以在不同的Java版本中生成和加载$Proxy类。最后,我们提到注解类型也是使用$Proxy类实现的。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章