Proxy of Java

最近在学习Java,学习到了一个新的Java特性——代理(Proxy)。代理可以为其它对象实现一种新的控制访问方式,这是Java SE1.3新加的特性。

目的

假如我们希望在运行时创建一个实现了一组给定接口的新类,我们就可以使用代理实现。

模式

代理类可以运行时创建全新的类,这个代理类能够实现指定的接口:

  • 指定接口所需要的全部方法
  • Object类中的全部方法

这个模式首先要让代理类和目标类实现相同的接口,然后客户端在通过代理类调用目标类的时候,代理类会将所有方法调用分派到目标对象上反射执行。分派过程中则可以在前后增加一些想要的功能。

步骤

  1. 通过实现InvocationHandler接口来自定义自己的handler,该接口有一个invoke的方法;
  2. 使用Proxy类的newProxyInstance创建一个代理对象,并将handler传入该对象;
  3. 通过代理对象调用目标方法;

原理

首先来看一个实现例子,这个程序会打印出被调用的参数和名字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import java.lang.reflect.*;
import java.util.*;

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;

InvocationHandler handler = new TraceHandler(value);
Object proxy = Proxy.newProxyInstance(null, new Class[]{Comparable.class}, handler);
elements[i] = proxy;
}

Integer rand = new Random().nextInt(elements.length) + 1;

int res = Arrays.binarySearch(elements, rand);

if (res > 0)
System.out.println(elements[res]);

}
}


class TraceHandler implements InvocationHandler
{
private Object target;

public TraceHandler(Object t)
{
target = t;
}

public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
{
System.out.print(target);

System.out.print("." + m.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 m.invoke(target, args);
}
}

/*
Ouput:
500.compareTo(155)
250.compareTo(155)
125.compareTo(155)
187.compareTo(155)
156.compareTo(155)
140.compareTo(155)
148.compareTo(155)
152.compareTo(155)
154.compareTo(155)
155.compareTo(155)
155.toString()
155
*/

通过打印结果可以得知,每次调用代理对象的compareTo方法,都会调用InvocationHandler的invoke方法。这是因为JVM动态生成的代理类生成代理对象时,会传入InvocationHandler实例对象。

然后我们在invoke方法内部通过Method.invoke()方法打印方法名称和参数来进行调用。

特性

代理类只有一个实例域,那就是InvocationHandler。另外,所有的代理类都覆盖了Object类中的方法toString,equals和hashCode。

对于特定的类加载器和预设的一组接口,只能有一个代理类。