利用代理可以在运行时创建一个实现了一组给定接口的新类。

何时使用代理

假设有一个表示接口的 Class 对象(有可能只包含一个接口),它的确切类型在编译时无法知道。这确实有些难度。要想构造一个实现这些接口的类,就需要使用 newInstance 方法或反射找出这个类的构造器。但是,不能实例化一个接口,需要在程序处于运行状态时定义一个新类
代理就是来解决上述问题的。代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。尤其是,它具有下列方法:

  • 指定接口所需要的全部方法。
  • Object类中的全部方法,例如,toString、equals等。

不能在运行时定义这些方法的新代码。而是要提供一个调用处理器(invocation handler)。调用处理器是实现了 InvocationHandler 接口的类对象。在这个接口中只有一个方法:

  1. Object invoke(Object proxy, Method method, Object[] args)

无论何时调用代理对象的方法,调用处理器的 invoke 方法都会被调用,并向其传递 Method 对象和原始的调用参数(args)。

创建代理对象

创建一个代理对象,需要使用 Proxy 类的 newProxyInstance 方法。这个方法有三个参数:

  • 一个类加载器(class loader)。作为 Java 安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。可以用 null 表示使用默认的类加载器,但要保证其类有默认的类加载器。
  • 一个 Class 对象数组,每个元素都是需要实现的接口。
  • 一个调用处理器。

比如,我们可以定义一个 TraceHander 包装器类存储包装的对象。其中的 invoke 方法打印出被调用方法的名字和参数,随后用包装好的对象作为隐式参数调用这个方法:

  1. /**
  2. * An invocation handler that prints out the method name and parameters, then
  3. * invokes the original method
  4. */
  5. public class TraceHandler implements InvocationHandler
  6. {
  7. private Object target;
  8. /**
  9. * Constructs a TraceHandler
  10. * @param t the implicit parameter of the method call
  11. */
  12. public TraceHandler(Object t)
  13. {
  14. target = t;
  15. }
  16. public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
  17. {
  18. // print implicit argument
  19. System.out.print(target);
  20. // print method name
  21. System.out.print("." + m.getName() + "(");
  22. // print explicit arguments
  23. if (args != null)
  24. {
  25. for (int i = 0; i < args.length; i++)
  26. {
  27. System.out.print(args[i]);
  28. if (i < args.length - 1) System.out.print(", ");
  29. }
  30. }
  31. System.out.println(")");
  32. // invoke actual method
  33. return m.invoke(target, args);
  34. }
  35. }

就可以构造用于跟踪方法调用的代理对象:

  1. /**
  2. * This program demonstrates the use of proxies.
  3. */
  4. public class ProxyTest
  5. {
  6. public static void main(String[] args)
  7. {
  8. Object[] elements = new Object[1000];
  9. // fill elements with proxies for the integers 1 ... 1000
  10. for (int i = 0; i < elements.length; i++)
  11. {
  12. Integer value = i + 1;
  13. InvocationHandler handler = new TraceHandler(value);
  14. Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class } , handler);
  15. // or
  16. // Object proxy = Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class[]{Comparable.class}, handler);
  17. elements[i] = proxy;
  18. }
  19. // construct a random integer
  20. Integer key = new Random().nextInt(elements.length) + 1;
  21. // search for the key
  22. int result = Arrays.binarySearch(elements, key);
  23. // print match if found
  24. if (result >= 0) System.out.println(elements[result]);
  25. }
  26. }

现在,无论何时用 elements 数组调用某个方法,这个方法的名字和参数就会打印出来,之后再用 value 调用它。
上述程序是使用代理对象对二分查找进行跟踪。这里,首先将用 1~1000 整数的代理填充数组,然后调用 Arrays 类中的 binarySearch 方法在数组中查找一个随机整数。最后,打印出与之匹配的元素。
Integer 类实现了 Comparable 接口。代理对象(即 proxy 变量)属于在运行时定义的类(它有一个名字,如$Proxy0)。这个类也实现了 Comparable 接口。然而,它的 compareTo 方法调用了代理对象处理器的 invoke 方法(也就是调用 compareTo() 时,会调用绑定的对象处理器的 invoke())。
下面是程序运行的全部跟踪结果:

  1. 500.compareTo(76)
  2. 250.compareTo(76)
  3. 125.compareTo(76)
  4. 62.compareTo(76)
  5. 93.compareTo(76)
  6. 77.compareTo(76)
  7. 69.compareTo(76)
  8. 73.compareTo(76)
  9. 75.compareTo(76)
  10. 76.compareTo(76)
  11. 76.toString() // look this
  12. 76

代理类的特性

代理类是在程序运行过程中创建的。然而,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有什么区别。
所有的代理类都扩展于 Proxy 类。一个代理类只有一个实例域————调用处理器,它定义在Proxy的超类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。
所有的代理类都覆盖了 Object 类中的方法 toString、equals 和 hashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的 invoke。Object 类中的其他方法(如 clone 和 getClass)没有被重新定义。
对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次 newProxyInstance 方法的话,那么只能够得到同一个类的两个对象,也可以利用 getProxyClass 方法获得这个类:

  1. Class proxyClass = Proxy.getProxyClass(null, interfaces);

代理类一定是 public 和 final。如果代理类实现的所有接口都是 public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。