本文共 3783 字,大约阅读时间需要 12 分钟。
代理(proxy),是Java SE 1.3新增加的特性。利用代理可以在运行时创建一个实现了一组给定接口的新类。
这种功能只有编译时无法确定需要实现哪个接口时才有必要使用。 使用场景: 有一个表示接口的Class对象(有可能只包含一个接口),它的确切类型在编译时无法知道。要想构造一个实现这些接口的类,就需要使用newInstance方法或反射找出这个类的构造器。但是,不能实例化一个接口,需要在程序运行状态时定义一个新类。 解决办法: (1)有些程序会生成代码,将这些代码放置在一个文件中;调用编译器,然后再加载结果类文件。这样做速度会比较慢,并且需要将编译器与程序放在一起。 (2)通过代理机制,代理类可以在运行时创建全新的类,这样的代理类能够实现指定的接口。 代理类具有下列方法: (1)指定接口所需的全部方法。 (2)Object类中的全部方法,例如toString、equals等。 不能在运行时定义这些方法的新代码。而是要提供一个调用处理器(invocation handler)。 调用处理器是实现了InvocationHandler接口的类对象。在这个接口中只有一个方法:Object invoke(Object proxy, Method method, Object[] args) 无论何时调用代理方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。调用处理器必须给出处理调用的方式。 要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这方法有三个参数: newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 一个类加载器(class loader)。作为Java安全模型的一部分,对于系统类或其他类,可以使用不同的类加载器(e.g. Foo.class.getClassLoader())。用 null 表示使用默认的类加载器。 一个Class对象数组,每个元素都是需要实现的接口。 一个调用处理器。 如何定义一个处理器,能够用结果代理对象做些什么? 答案取决于打算使用代理机制解决什么问题。使用代理可能出于什么原因,例如: (1)路由对远程服务器的方法调用。 (2)在程序运行期间,将用户接口事件与动作关联起来。 (3)为调试,跟踪方法调用。 DEMO 使用代理和调用处理器跟踪方法调用,并且定义了一个TraceHander包装器类存储包装的对象。其中invoke方法打印被调用方法的名字和参数,随后用包装好的对象作为隐式参数调用这个方法。import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.Arrays;import java.util.Random;public class ProxyTest { public static void main(String[] args) { Object[] elements = new Object[1000]; for(int i=0;i= 0){ System.out.println(elements[result]); } }}
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class TraceHandler implements InvocationHandler { private Object target; public TraceHandler(Object t){ target = t; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.print(target); System.out.print("." + method.getName() + "("); if(args != null){ for(int i=0;i在上面的DEMO中,无论何时调用proxy调用某个方法,这个方法的名字和参数就会打印出来,之后再用value调用它。 这里使用代理对象对二分查找进行跟踪。首先将用1~1000整数的代理填充数组,然后调用Arrays类中的binarySearch方法在数组中查找一个随机整数。最后,打印出与之匹配的元素。 这里,Integer类实现了Comparable接口,然而,它的compareTo方法调用了代理对象处理器的invoke方法。 注:Java SE 5.0中,Integer类实际上实现了Comparable<Integer>。然而,在运行时,所有的泛型类都被取消,代理将它们构造为原Comparable类的类对象。 binarySearch方法按下面这种方式调用:if(elements[i].compareTo(key) < 0) 由于数组中填充了代理对象,所以compareTo调用了TraceHander类中的invoke方法。这个方法打印出了方法名和参数,之后用包装好的Integer对象调用compareTo。 最后,在结尾调用: System.out.println(elements[result]); println方法调用代理对象的toString,这个调用也会被重定向到调用处理器上。 代理类的特性 1.代理类是在程序运行过程中创建的,然而,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有什么区别。 2.所有的代理类都扩展与Proxy类,一个代理类只有一个属性,即调用处理器。它定义在Proxy的超类中,为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。如DEMO中,代理Comparable对象时,TraceHandler包装了实际的对象。 3.所有的代理类都覆盖了Object类中的方法toString、equals和hashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invoke。Object类中的其他方法(如clone和getClass)没有被重新定义。 4.没有定义代理类的名字,Sun虚拟机中的Proxy类将生成一个以字符串$Proxy开头的类名。 5.对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,只能够得到同一个类的两个对象,也可以利用getProxyClass方法获得这个类。 Class proxyClass = Proxy.getProxyClass(null, interfaces); 6.代理类一定是public和final。如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。 7.可以通过调用Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表一个代理类。 涉及API 接口 java.lang.reflect.InvocationHandler Object invoke(Object proxy, Method method, Object[] args) : 在代理实例上处理方法调用并返回结果。 类 java.lang.reflect.Proxy static InvocationHandler getInvocationHandler(Object proxy) : 返回指定代理实例的调用处理程序。 static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) : 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。 static boolean isProxyClass(Class<?> cl) : 当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。也就是说c是一个代理类时返回true。 static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) : 返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
转载地址:http://koyci.baihongyu.com/