Module java.base

Class Proxy

java.lang.Object
java.lang.reflect.Proxy
所有已实现的接口:
Serializable

public class Proxy extends Object implements Serializable
代理提供了用于创建行为类似于接口实例但允许自定义方法调用的对象的静态方法。要为某个接口Foo创建代理实例:

     InvocationHandler handler = new MyInvocationHandler(...);
     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                          new Class<?>[] { Foo.class },
                                          handler);
 

代理类是在运行时创建的一个类,实现了指定的接口列表,称为代理接口。一个代理实例是代理类的实例。每个代理实例都有一个关联的调用处理程序对象,该对象实现了接口InvocationHandler。通过其代理接口之一对代理实例进行方法调用将被分派到实例的调用处理程序的invoke方法,传递代理实例、标识被调用的方法的java.lang.reflect.Method对象和包含参数的Object类型数组。调用处理程序将适当地处理编码的方法调用,并返回其返回的结果将作为代理实例上的方法调用的结果返回。

代理类具有以下属性:

  • 代理类的未限定名称是未指定的。以字符串"$Proxy"开头的类名空间应保留给代理类。
  • 代理类定义的包和模块是指定的,如下所示
  • 代理类是final和非抽象的。
  • 代理类扩展java.lang.reflect.Proxy
  • 代理类确切地实现了在其创建时指定的接口,顺序相同。在其Class对象上调用getInterfaces将返回一个包含相同接口列表的数组(按创建时指定的顺序),在其Class对象上调用getMethods将返回一个包含所有这些接口中的所有方法的Method对象数组,并调用getMethod将在代理接口中找到方法,就像预期的那样。
  • 代理类的ProtectionDomain与由引导类加载器加载的系统类(例如java.lang.Object)的保护域相同,因为代理类的代码是由受信任的系统代码生成的。此保护域通常将被授予java.security.AllPermission
  • 可以使用Proxy.isProxyClass方法来确定给定类是否为代理类。

代理实例具有以下属性:

  • 给定代理实例proxy和其代理类实现的接口之一Foo,以下表达式将返回true:
         proxy instanceof Foo
     
    并且以下强制转换操作将成功(而不会抛出ClassCastException):
         (Foo) proxy
     
  • 每个代理实例都有一个关联的调用处理程序,即传递给其构造函数的调用处理程序。静态的Proxy.getInvocationHandler方法将返回与传递给其参数的代理实例关联的调用处理程序。
  • 对代理实例的接口方法调用将被编码并分派到调用处理程序的invoke方法,如该方法的文档中所述。
  • 代理接口可以定义默认方法或直接或间接继承其超接口的默认方法。调用处理程序可以通过调用InvocationHandler::invokeDefault来调用代理接口的默认方法。
  • 对代理实例上声明在java.lang.Object中的hashCodeequalstoString方法的调用将被编码并分派到调用处理程序的invoke方法,方式与接口方法调用被编码和分派的方式相同,如上所述。传递给invokeMethod对象的声明类将是java.lang.Object。从java.lang.Object继承的代理实例的其他公共方法不会被代理类覆盖,因此对这些方法的调用的行为与java.lang.Object的实例的行为相同。

代理类的包和模块成员资格

代理类所属的包和模块是选择的,以使代理类的可访问性符合代理接口的可访问性。具体而言,通过getProxyClass(ClassLoader, Class[])newProxyInstance(ClassLoader, Class[], InvocationHandler)方法定义的代理类的包和模块成员资格如下:
  1. 如果所有代理接口都在导出开放包中:
    1. 如果所有代理接口都是public的,则代理类是在一个无条件导出但非开放包中的public类。包和模块的名称未指定。
    2. 如果所有代理接口中至少有一个是非public的,则代理类是在非公共接口的包和模块中的非public类。所有非公共接口必须在同一个包和模块中;否则,代理它们是不可能的
  2. 如果至少有一个代理接口在一个既非导出也非开放的包中:
    1. 如果所有代理接口都是public的,则代理类是在非导出非开放包中的public类,属于动态模块包和模块的名称未指定。
    2. 如果所有代理接口中至少有一个是非public的,则代理类是在非公共接口的包和模块中的非public类。所有非公共接口必须在同一个包和模块中;否则,代理它们是不可能的

请注意,如果代理接口具有混合可访问性的情况——例如,一个导出的公共接口和一个非导出的非公共接口——由最不可访问的代理接口决定代理类的可访问性。

请注意,任意代码可以通过setAccessible获取一个在开放包中的代理类,而在非开放包中的代理类永远不会对代理类所在模块之外的代码可访问。

在本规范中,“非导出包”指的是未向所有模块导出的包,“非开放包”指的是未向所有模块开放的包。具体而言,这些术语指的是包要么未被其包含模块导出/开放,要么以有资格的方式被其包含模块导出/开放。

动态模块

动态模块是在运行时生成的命名模块。在动态模块中定义的代理类是封装的,对任何模块都不可访问。在动态模块中对代理类调用Constructor.newInstance(Object...)将抛出IllegalAccessException;应该使用Proxy.newProxyInstance方法。

动态模块可以读取代理类的所有超接口的模块以及代理类的所有公共方法签名引用的类和接口的模块。如果超接口或引用的类或接口,比如T,在一个非导出包中,则T的模块将被更新为将T的包导出到动态模块。

多个代理接口中重复的方法

当两个或多个代理接口包含具有相同名称和参数签名的方法时,代理类接口的顺序变得重要。当在代理实例上调用这样的重复方法时,传递给调用处理程序的Method对象不一定是声明类可分配的那个接口的方法。存在此限制是因为生成的代理类中的相应方法实现无法确定通过哪个接口调用它。因此,当在代理实例上调用重复方法时,将传递包含该方法的最前面接口的Method对象(直接或通过超接口继承)到调用处理程序的invoke方法,而不管通过哪种引用类型进行方法调用。

如果代理接口包含与java.lang.ObjecthashCodeequalstoString方法具有相同名称和参数签名的方法,则在代理实例上调用此类方法时,传递给调用处理程序的Method对象将具有java.lang.Object作为其声明类。换句话说,java.lang.Object的公共、非最终方法在逻辑上优先于所有代理接口,用于确定传递给调用处理程序的Method对象。

还要注意,当重复的方法被分派给调用处理程序时,invoke方法只能抛出可分配给代理接口中throws子句中的异常类型之一的已检查异常类型。如果invoke方法抛出一个已检查异常,而该异常类型不可分配给可以通过其中一个代理接口调用的方法声明的任何异常类型,则代理实例上的调用将抛出一个未检查的UndeclaredThrowableException。这个限制意味着通过调用Method对象传递给invoke方法的getExceptionTypes可能无法成功地由invoke方法抛出的所有异常类型。

自 JDK 版本:
1.3
参见:
  • Field Details

  • Constructor Details

    • Proxy

      protected Proxy(InvocationHandler h)
      用指定的调用处理程序值从子类(通常是动态代理类)构造一个新的Proxy实例。
      参数:
      h - 该代理实例的调用处理程序
      抛出:
      NullPointerException - 如果给定的调用处理程序hnull
  • Method Details

    • getProxyClass

      @Deprecated public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException
      Deprecated.
      Proxy classes generated in a named module are encapsulated and not accessible to code outside its module. Constructor.newInstance will throw IllegalAccessException when it is called on an inaccessible proxy class. Use newProxyInstance(ClassLoader, Class[], InvocationHandler) to create a proxy instance instead.
      给定类加载器和接口数组,返回代理类的java.lang.Class对象。代理类将由指定的类加载器定义,并实现所有提供的接口。如果给定的接口中有任何非公共接口,则代理类将是非公共的。如果同一接口排列的代理类已由类加载器定义,则将返回现有的代理类;否则,将动态生成并由类加载器定义这些接口的代理类。
      参数:
      loader - 定义代理类的类加载器
      interfaces - 代理类要实现的接口列表
      返回:
      由指定类加载器定义并实现指定接口的代理类
      抛出:
      IllegalArgumentException - 如果违反参数的任何限制
      SecurityException - 如果存在安全管理器s,并且满足以下任一条件:
      • 给定的loadernull,并且调用者的类加载器不为null,并且使用RuntimePermission("getClassLoader")权限调用s.checkPermission拒绝访问。
      • 对于每个代理接口intf,调用者的类加载器与intf的类加载器不同或不是intf的类加载器的祖先,并且调用s.checkPackageAccess()拒绝访问intf
      NullPointerException - 如果interfaces数组参数或其任何元素为null
      参见:
    • newProxyInstance

      public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
      返回指定接口的代理实例,将方法调用分派给指定的调用处理程序。

      IllegalArgumentException将在违反以下任一限制时抛出:

      • 给定的interfaces数组中的所有Class对象必须表示非隐藏非密封接口,而不是类或原始类型。
      • interfaces数组中的任何两个元素不得引用相同的Class对象。
      • 所有接口类型必须通过指定的类加载器按名称可见。换句话说,对于类加载器cl和每个接口i,以下表达式必须为真:

        Class.forName(i.getName(), false, cl) == i

      • 指定接口的所有公共方法签名引用的类型以及它们的超级接口继承的类型必须通过指定的类加载器按名称可见。
      • 所有非公共接口必须位于相同的包和模块中,由指定的类加载器定义,并且非公共接口的模块可以访问所有接口类型;否则,无论代理类定义在哪个包中,都不可能使代理类实现所有接口。
      • 对于具有相同签名的指定接口的成员方法集合:
        • 如果任何方法的返回类型是原始类型或void,则所有方法必须具有相同的返回类型。
        • 否则,其中一个方法的返回类型必须可分配给其余方法的所有返回类型。
      • 生成的代理类不得超过虚拟机对类施加的任何限制。例如,虚拟机可能将类实现的接口数量限制为65535;在这种情况下,interfaces数组的大小不得超过65535。

      请注意,指定代理接口的顺序很重要:对于具有相同接口组合但顺序不同的两个代理类的请求将导致两个不同的代理类。

      参数:
      loader - 定义代理类的类加载器
      interfaces - 代理类要实现的接口列表
      返回:
      具有指定调用处理程序的代理实例,该代理实例由指定类加载器定义并实现指定接口
      抛出:
      IllegalArgumentException - 如果违反参数的任何限制
      SecurityException - 如果存在安全管理器s,并且满足以下任一条件:
      • 给定的loadernull,并且调用者的类加载器不为null,并且使用RuntimePermission("getClassLoader")权限调用s.checkPermission拒绝访问;
      • 对于每个代理接口intf,调用者的类加载器与intf的类加载器不同或不是intf的类加载器的祖先,并且调用s.checkPackageAccess()拒绝访问intf
      • 任何给定的代理接口是非公共的,调用类不在与非公共接口相同的运行时包中,并且使用ReflectPermission("newProxyInPackage.{package name}")权限调用s.checkPermission拒绝访问。
      NullPointerException - 如果interfaces数组参数或其任何元素为null,或者调用处理程序hnull
      参见:
    • isProxyClass

      public static boolean isProxyClass(Class<?> cl)
      如果给定类是代理类,则返回true。
      实现注意:
      该方法的可靠性对于能够用它做出安全决策很重要,因此其实现不应仅仅测试所讨论的类是否扩展Proxy
      参数:
      cl - 要测试的类
      返回:
      如果类是代理类,则返回true,否则返回false
      抛出:
      NullPointerException - 如果clnull
    • getInvocationHandler

      public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException
      返回指定代理实例的调用处理程序。
      参数:
      proxy - 要返回其调用处理程序的代理实例
      返回:
      代理实例的调用处理程序
      抛出:
      IllegalArgumentException - 如果参数不是代理实例
      SecurityException - 如果存在安全管理器s,并且调用者的类加载器与调用处理程序的类加载器不同或不是其类加载器的祖先,并且调用s.checkPackageAccess()拒绝访问调用处理程序的类。