利用代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。
对于应用程序设计人员来说,遇到这种情况的机会很少。然而,对于系统程序设计人员来说,代理带来的灵活性却十分重要。
假设有一个表示接口的Class对象数组(有可能只包含一个接口),它的确切类型在编译时无法知道。现在,想要构造一个实现这些接口的类对象,确实有些难度。如果一个Class对象表示一个实际的类,那么就可以使用newInstance方法或反射找出这个类的构造器。但是,不能实例化一个接口,并且在程序处于运行状态时也无法定义一个新类。
为了解决这个问题,有些程序将会生成代码;将这些代码放置在一个文件中;调用编译器;然后再加载结果类文件,Bean开发工具箱早期版本中的BeanBox就是一个例子。很自然,这样做的速度会比较慢,并且需要将编译器与程序放在一起。而代理机制则是一种更好的解决方案。代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。尤其是,它具有下列方法:
1.指定接口所需要的全部方法
2.Object类中的全部方法,例如 toString、equals等
然而,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器(invocation handler)。调用处理器是实现了InvocationHandler接口的类对象。在这个接口中只有一个方法:Object invoke(Object proxy, Method method, Object[] args)。
无论何时调用处理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。调用处理器必须给出处理调用的方式。
要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。
Object Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
这个方法有三个参数:
1.一个类加载器(class loader)。作为Java安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。
2.一个Class对象数组,每个元素都是需要实现的接口。
3.一个调用处理器。
还有两个需要解决的问题。如何定义一个处理器?能够用结果代理对象做些什么?当然,这两个问题的答案取决于打算使用代理机制解决什么问题。使用代理可能出于很多原因,例如:
1.路由对远程服务器的方法调用
2.在程序运行期间,将用户接口事件与动作关联起来
3.调试时跟踪方法调用
下面举个例子,在例子中使用代理和调用处理器跟踪 方法调用,并且定义了一个TraceHandler包装器类存储包装的对象。其中的invoke方法打印出被调用方法的名字和参数,随后用包装好的对象作为隐匿参数调用这个方法。
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 { //print method name and parameters //. . . //invoke actual method return method.invoke(target, args); } public static void main(String[] args){ Object value = "test"; //construct wrapper InvocationHandler handler = new TraceHandler(value); //construct proxy for all interfaces Class[] interfaces = value.getClass().getInterfaces(); Object proxy = Proxy.newProxyInstance(null, interfaces, handler); } }以上代码main部分说明了如何构造用于跟踪 方法调用 的代理对象。
现在,无论何时用proxy调用某个方法,这个方法的名字和参数就会打印出来,之后再用value调用它。
下面看一个完整的例子:
/** * This program demonstrates the use of proxies. */ public class ProxyTest { public static void main(String[] args) { Object[] elements = new Object[1000]; // fill elements with proxies for the integers 1 ... 1000 for (int i = 0; i < elements.length; i++) { Integer value = i + 1; InvocationHandler handler = new TraceHandler(value); Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler); elements[i] = proxy; } // construct a random integer Integer key = new Random().nextInt(elements.length) + 1; System.out.println("key="+key); // search for the key int result = Arrays.binarySearch(elements, key); // print match if found if (result >= 0) System.out.println("found:"+elements[result]); } } /** * An invocation handler that prints out the method name and parameters, then * invokes the original method */ class TraceHandler implements InvocationHandler { private Object target; /** * Constructs a TraceHandler * @param t the implicit parameter of the method call */ public TraceHandler(Object t) { target = t; } public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { // print implicit argument System.out.print(target); // print method name System.out.print("." + m.getName() + "("); // print explicit arguments 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(")"); // invoke actual method return m.invoke(target, args); } }
某一次运行的输出结果:
key=901
500.compareTo(901)
750.compareTo(901)
875.compareTo(901)
938.compareTo(901)
906.compareTo(901)
890.compareTo(901)
898.compareTo(901)
902.compareTo(901)
900.compareTo(901)
901.compareTo(901)
901.toString()
found:901
上例中,使用代理对象对二分查找进行跟踪。这里首先将用1-1000整数的代理填充数组,然后调用Arrays类中的binarySearch方法在数组中查找一个随机整数。最后,打印出与之匹配的元素。上述代码中,Integer类实现了Comparable接口。代理对象属于在运行时定义的类。(它有一个名字,如$Proxy0。)这个类也实现了Comparable接口。然而,它的compareTo方法调用了代理对象处理器的invoke方法。
代理类的特性:
现在,我们已经看到了代理类的应用,接下来了解它们的一些特性。需要记住,代理类是在程序运行过程中创建的。然而,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有什么区别。
所有的代理类都扩展于Proxy类。一个代理类只有一个实例变量——调用处理器,它定义在Proxy的超类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。在上例中,代理Comparable对象时,TraceHandler包装了实际的对象。
所有的代理类都覆盖了Object类中的方法toString、equals和hashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invoke。Object类中的其他方法(如clone和getClass)没有被重新定义。
没有定义代理类的名字,Sun虚拟机中的Proxy类将生成一个以字符串$Proxy开头的类名。
对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,只能够得到同一个类的两个对象,也可以利用getProxyClass方法获得这个类:
Class proxyClass = Proxy.getProxyClass(null, interfaces);
代理类一定是public final的。如果代理类实现的所有接口都是public,那么代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。
可以通过调用Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表一个代理类。
主要的方法: