代理機制及其特點
首先讓我們來了解一下如何使用 Java 動態代理。具體有如下四步驟:
- 通過實現 InvocationHandler 接口創建自己的調用處理器;
- 通過爲 Proxy 類指定 ClassLoader 對象和一組 interface 來創建動態代理類;
- 通過反射機制獲得動態代理類的構造函數,其唯一參數類型是調用處理器接口類型;
- 通過構造函數創建動態代理類實例,構造時調用處理器對象作爲參數被傳入。
清單 3. 動態代理對象創建過程
// InvocationHandlerImpl 實現了 InvocationHandler 接口,並能實現方法調用從代理類到委託類的分派轉發 // 其內部通常包含指向委託類實例的引用,用於真正執行分派轉發過來的方法調用 InvocationHandler handler = new InvocationHandlerImpl(..); // 通過 Proxy 爲包括 Interface 接口在內的一組接口動態創建代理類的類對象 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); // 通過反射從生成的類對象獲得構造函數對象 Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); // 通過構造函數對象創建動態代理類實例 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
實際使用過程更加簡單,因爲 Proxy 的靜態方法 newProxyInstance 已經爲我們封裝了步驟 2 到步驟 4 的過程,所以簡化後的過程如下
清單 4. 簡化的動態代理對象創建過程
// InvocationHandlerImpl 實現了 InvocationHandler 接口,並能實現方法調用從代理類到委託類的分派轉發 InvocationHandler handler = new InvocationHandlerImpl(..); // 通過 Proxy 直接創建動態代理類實例 Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );
機制和特點都介紹過了,接下來讓我們通過源代碼來了解一下 Proxy 到底是如何實現的。
首先記住 Proxy 的幾個重要的靜態變量:
清單 5. Proxy 的重要靜態變量
// 映射表:用於維護類裝載器對象到其對應的代理類緩存 private static Map loaderToCache = new WeakHashMap(); // 標記:用於標記一個動態代理類正在被創建中 private static Object pendingGenerationMarker = new Object(); // 同步表:記錄已經被創建的動態代理類類型,主要被方法 isProxyClass 進行相關的判斷 private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); // 關聯的調用處理器引用 protected InvocationHandler h;
然後,來看一下 Proxy 的構造方法:
清單 6. Proxy 構造方法
// 由於 Proxy 內部從不直接調用構造函數,所以 private 類型意味着禁止任何調用 private Proxy() {} // 由於 Proxy 內部從不直接調用構造函數,所以 protected 意味着只有子類可以調用 protected Proxy(InvocationHandler h) {this.h = h;}
接着,可以快速瀏覽一下 newProxyInstance 方法,因爲其相當簡單:
清單 7. Proxy 靜態方法 newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 檢查 h 不爲空,否則拋異常 if (h == null) { throw new NullPointerException(); } // 獲得與制定類裝載器和一組接口相關的代理類類型對象 Class cl = getProxyClass(loader, interfaces); // 通過反射獲取構造函數對象並生成代理類實例 try { Constructor cons = cl.getConstructor(constructorParams); return (Object) cons.newInstance(new Object[] { h }); } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }
由此可見,動態代理真正的關鍵是在 getProxyClass 方法,該方法負責爲一組接口動態地生成代理類類型對象。在該方法內部,您將能看到 Proxy 內的各路英雄(靜態變量)悉數登場。有點迫不及待了麼?那就讓我們一起走進 Proxy 最最神祕的殿堂去欣賞一番吧。該方法總共可以分爲四個步驟:
-
對這組接口進行一定程度的安全檢查,包括檢查接口類對象是否對類裝載器可見並且與類裝載器所能識別的接口類對象是完全相同的,還會檢查確保是 interface 類型而不是 class 類型。這個步驟通過一個循環來完成,檢查通過後將會得到一個包含所有接口名稱的字符串數組,記爲
String[] interfaceNames
。總體上這部分實現比較直觀,所以略去大部分代碼,僅保留留如何判斷某類或接口是否對特定類裝載器可見的相關代碼。清單 8. 通過 Class.forName 方法判接口的可見性
try { // 指定接口名字、類裝載器對象,同時制定 initializeBoolean 爲 false 表示無須初始化類 // 如果方法返回正常這表示可見,否則會拋出 ClassNotFoundException 異常表示不可見 interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { }
-
從 loaderToCache 映射表中獲取以類裝載器對象爲關鍵字所對應的緩存表,如果不存在就創建一個新的緩存表並更新到 loaderToCache。緩存表是一個 HashMap 實例,正常情況下它將存放鍵值對(接口名字列表,動態生成的代理類的類對象引用)。當代理類正在被創建時它會臨時保存(接口名字列表,pendingGenerationMarker)。標記 pendingGenerationMarke 的作用是通知後續的同類請求(接口數組相同且組內接口排列順序也相同)代理類正在被創建,請保持等待直至創建完成。
清單 9. 緩存表的使用
do { // 以接口名字列表作爲關鍵字獲得對應 cache 值 Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } if (proxyClass != null) { // 如果已經創建,直接返回 return proxyClass; } else if (value == pendingGenerationMarker) { // 代理類正在被創建,保持等待 try { cache.wait(); } catch (InterruptedException e) { } // 等待被喚醒,繼續循環並通過二次檢查以確保創建完成,否則重新等待 continue; } else { // 標記代理類正在被創建 cache.put(key, pendingGenerationMarker); // break 跳出循環已進入創建過程 break; } while (true);
-
動態創建代理類的類對象。首先是確定代理類所在的包,其原則如前所述,如果都爲 public 接口,則包名爲空字符串表示頂層包;如果所有非 public 接口都在同一個包,則包名與這些接口的包名相同;如果有多個非 public 接口且不同包,則拋異常終止代理類的生成。確定了包後,就開始生成代理類的類名,同樣如前所述按格式“$ProxyN”生成。類名也確定了,接下來就是見證奇蹟的發生 —— 動態生成代理類:
清單 10. 動態生成代理類
// 動態地生成代理類的字節碼數組 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { // 動態地定義新生成的代理類 proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } // 把生成的代理類的類對象記錄進 proxyClasses 表 proxyClasses.put(proxyClass, null);
由此可見,所有的代碼生成的工作都由神祕的 ProxyGenerator 所完成了,當你嘗試去探索這個類時,你所能獲得的信息僅僅是它位於並未公開的 sun.misc 包,有若干常量、變量和方法以完成這個神奇的代碼生成的過程,但是 sun 並沒有提供源代碼以供研讀。至於動態類的定義,則由 Proxy 的 native 靜態方法 defineClass0 執行。
- 代碼生成過程進入結尾部分,根據結果更新緩存表,如果成功則將代理類的類對象引用更新進緩存表,否則清楚緩存表中對應關鍵值,最後喚醒所有可能的正在等待的線程。
走完了以上四個步驟後,至此,所有的代理類生成細節都已介紹完畢,剩下的靜態方法如 getInvocationHandler 和 isProxyClass 就顯得如此的直觀,只需通過查詢相關變量就可以完成,所以對其的代 碼分析就省略了。
package com.hpf.proxy;
public interface People {
public void sayHello();
}
package com.hpf.proxy;
public class Student implements People{
@Override
public void sayHello() {
System. out.println( "student say hello");
}
}
package com.hpf.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler{
private Object object; //被代理的對象
public MyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System. out.println( "do before");
method.invoke( object, args);//調用原始對象的方法
System. out.println( "do after");
return null;
}
}
package com.hpf.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.junit.Test;
public class MyTestProxy {
@Test
public void testProxy(){
People people1= new Student();
InvocationHandler iHandler = new MyInvocationHandler(people1);
People people = (People) Proxy.newProxyInstance(people1.getClass().getClassLoader(),
new Class[]{People. class}, iHandler);
people.sayHello();//調用代理對象的方法,將會調用調用處理器的invoke方法
}
}
美中不足
有很多條理由,人們可以否定對 class 代理的必要性,但是同樣有一些理由,相信支持 class 動態代理會更美好。接口和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將因爲沒有實現任何接口而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。
但是,不完美並不等於不偉大,偉大是一種本質,Java 動態代理就是佐例。