Java的代理機制
使用代理 Proxzy 可以在執行時建立一組給定介面的新類,這種功能只有在編譯時無法確定需要實現哪種介面時才需要使用。
1. 使用代理的時機
假如有一個表示介面的 Class 物件,它的確切型別在編譯時無法得知。由於沒有實現類而只有一個介面,反射和newInstance
語句是無法例項化這個 Class 物件的,我們需要在程式處於執行狀態時定義一個新類。
代理類可以在執行時建立全新的類,這樣的代理類可以實現指定的介面,它具有下列方法:
- 指定介面所需要的全部方法。
- Object 類中的全部方法,例如
toString()
、equals()
等。
在代理機制中,不能再允許時定義這些方法的新程式碼,而是要提供一個呼叫處理器 InvocationHandler,呼叫處理器是實現了InvocationHandler
介面的類物件。在這個介面中只有一個方法:
Object invoke(Object proxy, Method method, Object[] args)
無論何時呼叫代理物件的方法,invoke()
方法都被呼叫,並向其傳遞 Method 物件和原始的呼叫引數,呼叫處理器必須給出處理呼叫的方式。
2. 建立代理物件
建立代理物件要使用 Proxy 類的newProxyInstance
方法,這個方法有三個引數:
- 一個類載入器。可以使用不同的類載入器,用 null 表示使用預設的類載入器。
- 一個 Class 物件陣列。每個元素都是需要實現的介面。
- 一個呼叫處理器。
下面給出一個示例程式,使用代理和呼叫處理跟蹤方法呼叫:
// 呼叫處理器
class TraceHandler implements InvocationHandler{
private Object target;
// 建構函式
public TraceHandler(Object t){
target = t;
}
// invoke方法
public Object invoke(Objcet proxy, Method m, Object[] args) throws Throwable {
// print method name and parameters
...
// invoke actual method
return m.invoke(target, args);
}
}
下面的程式碼,我們用於跟蹤方法呼叫的代理物件:
Object value = ...;
// 構造呼叫處理器
InvocationHandler handler = new TraceHandler(value);
// 構造代理物件
Class[] interfaces = new Class[](Comparable.class);
Object proxy = Proxy.newProxyInstance(null, interfaces, handler);
我們再用 proxy 任何方法時,都會呼叫invoke()
方法,列印出方法的名字和引數,再用value
物件呼叫它。
3. 代理類的特性
代理類有下面這樣一些特性:
- 所有的代理類都擴充套件於 Proxy 類。一個代理類只有一個例項域:呼叫處理器。
- 所需的任何附加資料儲存在呼叫處理器中,例如代理 Comparable 物件時,在
TraceHandler
中包裝了實際的物件target
。 - 所有的代理類都覆蓋了 Object 類中的方法
toString()
、equals()
、hashCode()
。如果所有的代理方法一樣,這些方法僅僅呼叫了呼叫處理器的invoke()
。Object 類中的其他方法沒有被重新定義。 - 對於特定的類載入器和一組預設的介面,最多隻能有一個代理類。也就是說如果用同樣的引數重複呼叫兩次
newProxyInstance()
方法,那麼只能得到一個類的兩個物件。 - 代理類一定是 public、final 。如果代理類實現的所有介面都是 public 的,代理類就不屬於某個特定的包。否則所有非公有的介面必須屬於同一個包,代理類也屬於這個包。
- 可以用 Proxy 類的
isProxyClass()
方法檢測一個特定的 Class 物件是否是一個代理類。