代理(proxy)
利用代理可以在運行時創建一個實現了一組給定接口的新類。這種功能只有在編譯時無法確定需要實現哪個接口時才有必要使用。
何時使用代理
假設有一個表示接口的Class對象(有可能只包含一個接口),它的確切類型在編譯時無法知道。要想構造一個實現這些接口的類,就需要使用newInstance方法或反射找出這個類的構造器。但是,不能實例化一個接口,需要在程序處於運行狀態時定義一個新類。
代理類可以在運行時創建全新的類。這樣的代理類能夠實現指定的接口。尤其是,它具有下列方法:
- 指定接口所需要的全部方法
- Object類中的全部方法,例如, toString, equals等。
創建代理對象
要想創建一個代理對象,需要使用Proxy類的newProxyInstance方法。這個方法有三個參數:
- 一個類加載器(class loader)。
- 一個Class對象數組,每個元素都是需要實現的接口。
- 一個調用處理器
還有兩個需要解決的問題。如何定義一個處理器?能夠用結果代理對象做些什么?當然,這兩個問題的答案取決於打算使用代理機制解決什么問題。比如
- 路由對遠程服務器的方法調用
- 調試,跟蹤
- log
Demo
我們定義一個處理器,用來打印調用的參數
public 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);
}
}
接下來,我們用它來代理Comparable接口,看看怎么調用。比如Arrays的二分查找方法binarySearch
@Test
public void traceBinarySearch() {
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 key = new Random().nextInt(elements.length) + 1;
int result = Arrays.binarySearch(elements, key);
if (result > 0) {
System.out.println(elements[result]);
}
}
控制台打印結果:
500.compareTo(94)
250.compareTo(94)
125.compareTo(94)
62.compareTo(94)
93.compareTo(94)
109.compareTo(94)
101.compareTo(94)
97.compareTo(94)
95.compareTo(94)
94.compareTo(94)
94.toString()
94
代理類的特性
- 代理類是在運行過程中創建的,創建完畢后和常規類相同,虛擬機同等對待。
- 所有的代理類都擴展於Proxy類。一個代理類只有一個實例域---調用處理器,它定義在Proxy的超類中。
- 沒有定義代理類的名字,Sun虛擬機中的Proxy類將生成一個以字符串$Proxy開頭的類名。
- 對於特定的類加載器和預設的一組接口來說,只能有一個代理類。也就是說,如果使用同一個類加載器和接口數組調用兩次newProxyInstance方法的話,只能得到同一個類的兩個對象。比如
class com.sun.proxy.$Proxy4
.可以使用getProxyClass
來獲取這個類。 - 代理類一定是public final的。
- 可以通過
Proxy.isProxyClass
方法檢測一個特定的Class對象是否代表一個代理類。
來源
- Java核心技術