jdk的動態代理是基於接口的,必須實現了某一個或多個任意接口纔可以被代理,並且只有這些接口中的方法會被代理。看了一下jdk帶的動態代理 api,發現沒有例子實在是很容易走彎路,所以這裏寫一個加法器的簡單示例。
// Adder.java
- package test;
- public interface Adder {
- int add( int a, int b);
- }
// AdderImpl.java
- package test;
- public class AdderImpl implements Adder {
- @Override
- public int add( int a, int b) {
- return a + b;
- }
- }
現在我們有一個接口Adder以及一個實現了這個接口的類AdderImpl,寫一個Test測試一下。
// Test.java
- package test;
- public class Test {
- public static void main(String[] args) throws Exception {
- Adder calc = new AdderImpl();
- int result = calc.add( 1 , 2 );
- System.out.println( "The result is " + result);
- }
- }
很顯然,控制檯會輸出:
The result is 3
然而現在我們需要在加法器使用之後記錄一些信息以便測試,但AdderImpl的源代碼不能更改,就像這樣:
Proxy: invoke add() at 2009-12-16 17:18:06
The result is 3
動態代理可以很輕易地解決這個問題。我們只需要寫一個自定義的調用處理器(實現接口 java.lang.reflect.InvokationHandler),然後使用類java.lang.reflect.Proxy中的靜態方法生成Adder的代理類,並把這個代理類當做原先的Adder使用就可以。
第一步:實現InvokationHandler,定義調用方法時應該執行的動作。
自定義一個類MyHandler實現接口 java.lang.reflect.InvokationHandler,需要重寫的方法只有一個:
// AdderHandler.java
- package test;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- class AdderHandler implements InvocationHandler {
- /**
- * @param proxy 接下來Proxy要爲你生成的代理類的實例,注意,並不是我們new出來的AdderImpl
- * @param method 調用的方法的Method實例。如果調用了add(),那麼就是add()的Method實例
- * @param args 調用方法時傳入的參數。如果調用了add(),那麼就是傳入add()的參數
- * @return 使用代理後將作爲調用方法後的返回值。如果調用了add(),那麼就是調用add()後的返回值
- */
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- // ...
- }
- }
使用代理後,這個方法將取代指定的所有接口中的所有方法的執行。在本例中,調用 adder.add()方法時,實際執行的將是invoke()。所以爲了有正確的結果,我們需要在invoke()方法中手動調用add()方法。再看看invoke()方法的參數,正好符合反射需要的所有條件,所以這時我們馬上會想到這樣做:
Object returnValue = method.invoke(proxy, args);
如果你真的這麼做了,那麼恭喜你,你掉入了jdk爲你精心準備的圈套。proxy是jdk爲你生成的代理類的實例,實際上就是使用代理之後 adder引用所指向的對象。由於我們調用了adder.add(1, 2),才使得invoke()執行,如果在invoke()中使用method.invoke(proxy, args),那麼又會使invoke()執行。沒錯,這是個死循環。然而,invoke()方法沒有別的參數讓我們使用了。最簡單的解決方法就是,爲 MyHandler加入一個屬性指向實際被代理的對象。所以,因爲jdk的冷幽默,我們需要在自定義的Handler中加入以下這麼一段:
- // 被代理的對象
- private Object target;
- public AdderHandler(Object target) {
- this .target = target;
- }
喜歡的話還可以加上getter/setter。接着,invoke()就可以這麼用了:
// AdderHandler.java
- package test;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.util.Date;
- class AdderHandler implements InvocationHandler {
- // 被代理的對象
- private Object target;
- public AdderHandler(Object target) {
- this .target = target;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- // 調用被代理對象的方法並得到返回值
- Object returnValue = method.invoke(target, args);
- // 調用方法前後都可以加入一些其他的邏輯
- System.out.println( "Proxy: invoke " + method.getName() + "() at " + new Date().toLocaleString());
- // 可以返回任何想要返回的值
- return returnValue;
- }
- }
第二步:使用jdk提供的java.lang.reflect.Proxy生成代理對象。
使用newProxyInstance()方法就可以生成一個代理對象。把這個方法的簽名拿出來:
- /**
- * @param loader 類加載器,用於加載生成的代理類。
- * @param interfaces 需要代理的接口。這些接口的所有方法都會被代理。
- * @param h 第一步中我們建立的Handler類的實例。
- * @return 代理對象,實現了所有要代理的接口。
- */
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces,
- InvocationHandler h)
- throws IllegalArgumentException
這個方法會做這樣一件事情,他將把你要代理的全部接口用一個由代碼動態生成的類類實現,所有的接口中的方法都重寫爲調用 InvocationHandler.invoke()方法。這個類的代碼類似於這樣:
- // 模擬Proxy生成的代理類,這個類是動態生成的,並沒有對應的.java文件。
- class AdderProxy extends Proxy implements Adder {
- protected AdderProxy(InvocationHandler h) {
- super (h);
- }
- @Override
- public int add( int a, int b) {
- try {
- Method m = Adder. class .getMethod( "add" , new Class[] { int . class , int . class });
- Object[] args = {a, b};
- return (Integer) h.invoke( this , m, args);
- } catch (Throwable e) {
- throw new RuntimeException(e);
- }
- }
- }
據api說,所有生成的代理類都是Proxy的子類。當然,生成的這個類的代碼你是看不到的,而且Proxy裏面也是調用sun.XXX包的api 生成;一般情況下應該是直接生成了字節碼。然後,使用你提供的ClassLoader將這個類加載並實例化一個對象作爲代理返回。
看明白這個方法後,我們來改造一下main()方法。
// Test.java
- package test;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Proxy;
- public class Test {
- public static void main(String[] args) throws Exception {
- Adder calc = new AdderImpl();
- // 類加載器
- ClassLoader loader = Test. class .getClassLoader();
- // 需要代理的接口
- Class[] interfaces = {Adder. class };
- // 方法調用處理器,保存實際的AdderImpl的引用
- InvocationHandler h = new AdderHandler(calc);
- // 爲calc加上代理
- calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h);
- /* 什麼?你說還有別的需求? */
- // 另一個處理器,保存前處理器的引用
- // InvocationHandler h2 = new XXOOHandler(h);
- // 再加代理
- // calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h2);
- int result = calc.add( 1 , 2 );
- System.out.println( "The result is " + result);
- }
- }
輸出結果會是什麼呢?
Proxy: invoke add() at 2009-12-16 18:21:33
The result is 3
對比一下之前的結果,你會發現這點東西寫了我一個多小時。再來看看JDK有多懶:target完全可以在代理類中生成。
實際方法都需要手動調用,可見代理類中重寫所有的方法都只有一句話:return xxx.invoke(ooo);不過這麼寫也有他的理由,target自己管理,方法你愛調不調 ﹃_﹃;如果他調了,InvocationHandler接口中恐怕就需要兩個方法了,還要判斷返回、處理參數等等。