利用代理可以在运行时创建实现了一组给定接口的新类。只有在编译时期无法确定需要实现哪个接口时才有必要使用代理。
何时使用代理
假设你想构造一个类的对象,这个类实现了一个或多个接口,但是在编译时你可能并不知道这些接口到底是什么。代理机制是一种更好的解决方案。代理类可以在运行时创建全新的类。这样的代理类能够实现你指定的接口。具体地,代理类包含以下方法:
- 指定接口所需要的全部方法。
- Object类的全部方法,例如,toString、equals等。
不过,不能在运行时为这些方法定义新代码。实际上,必须提供一个调用处理器(invocation handler)。调用处理器是实现了InvocationHandler接口的类的对象。这个接口只有一个方法:
Object invoke(Object proxy, Method method, Object[] args)
无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原调用的参数。之后调用处理器必须确定如何处理这个调用。
创建代理对象
要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这个方法有三个参数:
- 一个类加载器(class loader)。作为Java安全模型的一部分,可以对平台和应用类、从因特网下载的类等使用不同的类加载器。
- 一个Class对象数组,每个元素对应需要实现的各个接口。
- 一个调用处理器。
代理机制所解决的问题决定了如何定义处理器以及得到的代理对象能够做些什么。
下面示例使用代理和调用处理器跟踪方法调用。定义了一个TraceHandler包装器类存储包装的对象,其中的invoke方法会打印所调用方法的名字和参数,随后用包装的对象作为隐式参数调用这个方法。
package section5_5;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
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 < elements.length;i++) {
Integer value = i + 1;
TraceHandler handler = new TraceHandler(value);
Object proxy = Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[] { Comparable.class },
handler
);
elements[i] = proxy;
}
Integer key = new Random().nextInt(elements.length) + 1;
int result = Arrays.binarySearch(elements,key);
if (result >= 0) System.out.println(elements[result]);
}
}
class TraceHandler implements InvocationHandler {
private Object target;
public TraceHandler(Object target) {
this.target = target;
}
@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 < args.length;i++) {
System.out.print(args[i]);
if (i < args.length - 1) System.out.print(",");
}
}
System.out.println(")");
return method.invoke(target,args);
}
}
在示例中,只要在proxy上调用了某个接口的方法,就会打印这个方法的名字和参数,之后再用value调用这个方法。代理对象属于在运行时定义的一个类。这个类也实现了Comparable接口。不过,它的compareTo方法调用了代理对象处理器的invoke方法。
Integer类实际上实现了Comparable<Integer>。不过,在运行时,所有的泛型类型都会取消,会用对应原始Comparable类的类对象构造代理。
binarySearch方法会有以下调用:
if (elements[i].compareTo(key) < 0)...
由于数组中填充了代理对象,所以compareTo调用了TraceHandler类中的invoke方法。这个方法打印出了方法名和参数,之后在包装的Integer对象上调用compareTo。
最后,在示例程序的最后调用:
System.out.println(elements[result]);
这个println方法调用代理对象的toString,这个调用也会被重定向到调用处理器。
代理类的特性
代理类是在程序运行过程中动态创建的。然而,一旦被创建,它们就变成了常规类,与虚拟机中的任何其他类没有什么区别。
所有的代理类都扩展Proxy类。一个代理类只有一个实例字段——即调用处理器,它在Proxy超类中定义。完成代理对象任务所需要的任何额外数据都必须存储在调用处理器中。例如,上述示例中,代理Comparable对象时,TraceHandler就包装了实际的对象。
所有的代理类都要覆盖Object类的toString、equals和hashCode方法。如同所有代理方法一样,这些方法只是在调用处理器上调用invoke。Object类中的其他方法(如clone和getClass)没有重新定义。
对于一个特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxyInstance方法,将得到同一个类的两个对象。也可以用getProxyClass方法获得这个类:
Class proxyClass = Proxy.getProxyClass(null,interfaces);
代理类总是public和final。如果代理类实现的所有接口都是public,这个代理类就不属于任何特定的包;否则,所有非公共的接口都必须属于同一个包,同时,代理类也属于这个包。
可以通过调用Proxy类的isProxyClass方法检测一个特定的Class对象是否表示一个代理类。