java 動態代理實現原理
上篇講了:java動態代理淺析 這篇講講其內部實現原理。
1、相關的類和介面
1.1 java.lang.reflect.Proxy
這是 Java 動態代理機制的主類,它提供了一組靜態方法來為一組介面動態地生成代理類及其物件。
Proxy 的靜態方法:
// 方法 1: 該方法用於獲取指定代理物件所關聯的呼叫處理器 static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:該方法用於獲取關聯於指定類裝載器和一組介面的動態代理類的類物件 static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:該方法用於判斷指定類物件是否是一個動態代理類 static boolean isProxyClass(Class cl) // 方法 4:該方法用於為指定類裝載器、一組介面及呼叫處理器生成動態代理類例項 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) loader: 一個ClassLoader物件,定義了由哪個ClassLoader物件來對生成的代理物件進行載入 interfaces: 一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理物件就宣稱實現了該介面(多型),這樣我就能呼叫這組介面中的方法了 h: 一個InvocationHandler物件,表示的是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上
1.2 java.lang.reflect.InvocationHandler
這是呼叫處理器介面,它自定義了一個 invoke 方法,用於集中處理在動態代理類物件上的方法呼叫,通常在該方法中實現對委託類的代理訪問。
每次生成動態代理類物件時都需要指定一個實現了該介面的呼叫處理器物件,並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的
invoke 方法來進行呼叫。
// 該方法負責集中處理動態代理類上的所有方法呼叫 Object invoke(Object proxy, Method method, Object[] args) throws Throwable proxy: 指代我們所代理的那個真實物件 method: 指代的是我們所要呼叫真實物件的某個方法的Method物件 args: 指代的是呼叫真實物件某個方法時接受的引數</span>
2、動態代理實現過程:
具體有如下步驟:(最終目的得到動態代理類的例項物件)
1.建立自己的呼叫處理器:通過實現 InvocationHandler 介面建立自己的呼叫處理器;
2.建立動態代理類的類物件:通過為 Proxy 類指定 ClassLoader 物件和一組 interface 以及呼叫處理器來建立動態代理類;
3.獲得動態代理類的建構函式:通過反射機制獲得動態代理類的建構函式,其唯一引數型別是呼叫處理器介面型別;
4.建立動態代理類例項:通過建構函式建立動態代理類例項,構造時呼叫處理器物件作為引數被傳入。動態代理物件建立過程:
// 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 的過程,所以簡化後的過程如下:
簡化的動態代理物件建立過程
/ InvocationHandlerImpl 實現了 InvocationHandler 介面,並能實現方法呼叫從代理類到委託類的分派轉發
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過 Proxy 直接建立動態代理類例項
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );
3、Java 動態代理機制的一些特點
3.1動態生成的代理類本身的一些特點
3.1.1包
如果所代理的介面都是 public 的,那麼它將被定義在頂層包(即包路徑為空),如果所代理的介面中有非 public 的介面(因為介面不能被定義為 protect 或 private,所以除 public 之外就是預設的 package 訪問級別),那麼它將被定義在該介面所在包(假設代理了 com.ibm.developerworks 包中的某非 public 介面 A,那麼新生成的代理類所在的包就是 com.ibm.developerworks),這樣設計的目的是為了最大程度的保證動態代理類不會因為包管理的問題而無法被成功定義並訪問;
3.1.2類修飾符
該代理類具有 final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;
3.1.3類名
格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數字,代表 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,並不是每次呼叫 Proxy 的靜態方法建立動態代理類都會使得 N 值增加,原因是如果對同一組介面(包括介面排列的順序相同)試圖重複建立動態代理類,它會很聰明地返回先前已經建立好的代理類的類物件,而不會再嘗試去建立一個全新的代理類,這樣可以節省不必要的程式碼重複生成,提高了代理類的建立效率。
3.1.4類繼承關係
該類的繼承關係如圖:
由圖可見,Proxy 類是它的父類,這個規則適用於所有由 Proxy 建立的動態代理類。而且該類還實現了其所代理的一組介面,這就是為什麼它能夠被安全地型別轉換到其所代理的某介面的根本原因。
3.2 代理類例項的一些特點
3.2.1每個例項都會關聯一個呼叫處理器物件
可以通過 Proxy 提供的靜態方法 getInvocationHandler去獲得代理類例項的呼叫處理器物件。
3.2.2 代理類例項的方法
在代理類例項上呼叫其代理的介面中所宣告的方法時,這些方法最終都會由呼叫處理器的 invoke 方法執行,此外,值得注意的是,代理類的根類 java.lang.Object 中有三個方法也同樣會被分派到呼叫處理器的 invoke 方法執行,它們是 hashCode,equals 和 toString,
可能的原因有:
一是因為這些方法為 public 且非 final 型別,能夠被代理類覆蓋;
二是因為這些方法往往呈現出一個類的某種特徵屬性,具有一定的區分度,所以為了保證代理類與委託類對外的一致性,這三個方法也應該被分派到委託類執行。
當代理的一組介面有重複宣告的方法且該方法被呼叫時,代理類總是從排在最前面的介面中獲取方法物件並分派給呼叫處理器,而無論代理類例項是否正在以該介面(或繼承於該介面的某子介面)的形式被外部引用,因為在代理類內部無法區分其當前的被引用型別。
3.2.3 被代理的一組介面有哪些特點
首先,要注意不能有重複的介面,以避免動態代理類程式碼生成時的編譯錯誤。
其次,這些介面對於類裝載器必須可見,否則類裝載器將無法連結它們,將會導致類定義失敗。
再次,需被代理的所有非 public 的介面必須在同一個包中,否則代理類生成也會失敗。
最後,介面的數目不能超過 65535,這是 JVM 設定的限制。
3.2.4 異常處理方面的特點
從呼叫處理器介面宣告的方法中可以看到理論上它能夠丟擲任何型別的異常,因為所有的異常都繼承於 Throwable 介面,但事實是否如此呢?答案是否定的,原因是我們必須遵守一個繼承原則:即子類覆蓋父類或實現父介面的方法時,丟擲的異常必須在原方法支援的異常列表之內。所以雖然呼叫處理器理論上講能夠,但實際上往往受限制,除非父介面中的方法支援拋
Throwable 異常。那麼如果在 invoke 方法中的確產生了介面方法宣告中不支援的異常,那將如何呢?放心,Java 動態代理類已經為我們設計好了解決方法:它將會丟擲 UndeclaredThrowableException 異常。這個異常是一個 RuntimeException 型別,所以不會引起編譯錯誤。通過該異常的 getCause 方法,還可以獲得原來那個不受支援的異常物件,以便於錯誤診斷。
4、動態代理原始碼分析
4.1 簡單的動態代理實現
package dynamic.proxy;
/**
* 目標物件實現的介面,用JDK來生成代理物件一定要實現一個介面
*/
public interface UserService {
/**
* 目標方法
*/
public abstract void add();
}
package dynamic.proxy;
/**
* 目標物件
*/
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("--------------------add---------------");
}
}
package dynamic.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 實現自己的InvocationHandler
*/
public class MyInvocationHandler implements InvocationHandler {
// 目標物件
private Object target;
/**
* 構造方法
* @param target 目標物件
*/
public MyInvocationHandler(Object target) {
super();
this.target = target;
}
/**
* 執行目標物件的方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在目標物件的方法執行之前簡單的列印一下
System.out.println("------------------before------------------");
// 執行目標物件的方法
Object result = method.invoke(target, args);
// 在目標物件的方法執行之後簡單的列印一下
System.out.println("-------------------after------------------");
return result;
}
/**
* 獲取目標物件的代理物件
* @return 代理物件
*/
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(), this);
}
}
執行結果如下: ------------------before------------------
--------------------add---------------
-------------------after------------------
待弄清楚問題:
1.代理物件是怎麼生成的
2.InvocationHandler的invoke方法是由誰來呼叫的
4.2 Proxy類
Proxy 的重要靜態變數:
// 對映表:用於維護類裝載器物件到其對應的代理類快取
private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache = new WeakHashMap<>();
// 標記:用於標記一個動態代理類正在被建立中
private static Object pendingGenerationMarker = new Object();
// 同步表:記錄已經被建立的動態代理類型別,主要被方法 isProxyClass 進行相關的判斷
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());
// 關聯的呼叫處理器引用
protected InvocationHandler h;
Proxy 構造方法:
// 由於 Proxy 內部從不直接呼叫建構函式,所以 private 型別意味著禁止任何呼叫
private Proxy() {}
// 由於 Proxy 內部從不直接呼叫建構函式,所以 protected 意味著只有子類可以呼叫
protected Proxy(InvocationHandler h) {this.h = h;}
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 {
// 呼叫代理物件的構造方法(也就是$Proxy0(InvocationHandler h))
Constructor cons = cl.getConstructor(constructorParams);
// 生成代理類的例項並把MyInvocationHandler的例項傳給它的構造方法
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 方法,該方法負責為一組介面動態地生成代理類Class(型別)物件。
Proxy 靜態方法 getProxyClass:
該方法總共可以分為四個步驟:
1.對這組介面進行一定程度的安全檢查
包括檢查介面類物件是否對類裝載器可見並且與類裝載器所能識別的介面類物件是完全相同的,還會檢查確保是 interface 型別而不是 class 型別。這個步驟通過一個迴圈來完成,檢查通過後將會得到一個包含所有介面名稱的字串陣列,記為 String[] interfaceNames。總體上這部分實現比較直觀,所以略去大部分程式碼,僅保留留如何判斷某類或介面是否對特定類裝載器可見的相關程式碼。
通過 Class.forName 方法判介面的可見性:
try {
// 指定介面名字、類裝載器物件,同時制定 initializeBoolean 為 false 表示無須初始化類
// 如果方法返回正常這表示可見,否則會丟擲 ClassNotFoundException 異常表示不可見
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
2.從 loaderToCache 對映表中獲取以類裝載器物件為關鍵字所對應的快取表,如果不存在就建立一個新的快取表並更新到 loaderToCache。
快取表是一個 HashMap 例項,正常情況下它將存放鍵值對(介面名字列表,動態生成的代理類的類物件引用)。當代理類正在被建立時它會臨時儲存(介面名字列表,pendingGenerationMarker)。標記 pendingGenerationMarke 的作用是通知後續的同類請求(介面陣列相同且組內介面排列順序也相同)代理類正在被建立,請保持等待直至建立完成。
private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache = new WeakHashMap<>();
ClassLoader:類裝載器
Map<List<String>, Object>:List<String>
--- 介面名字列表 Object---動態生成的代理類的類物件引用
快取表的使用:
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);
3.動態建立代理類的Class(類)物件
1.首先是確定代理類所在的包,其原則如前所述,如果都為 public 介面,則包名為空字串表示頂層包;如果所有非 public 介面都在同一個包,則包名與這些介面的包名相同;如果有多個非 public 介面且不同包,則拋異常終止代理類的生成。
2.確定了包後,就開始生成代理類的類名,同樣如前所述按格式“$ProxyN”生成。
3.類名也確定了,接下來就是見證奇蹟的發生 ——動態生成代理類 (解決了4.1中待弄清楚問題1)
動態生成代理類
// 動態地生成代理類的位元組碼陣列
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 執行。
4.根據結果更新快取表
如果成功則將代理類的類物件引用更新進快取表,否則清楚快取表中對應關鍵值,最後喚醒所有可能的正在等待的執行緒。
JDK是怎樣動態生成代理類的位元組的原理已經一目瞭然了。再來解決另外一個問題,那就是由誰來呼叫InvocationHandler的invoke方法的。要解決這個問題就要看一下JDK到底為我們生成了一個什麼東西。我們可以import sun.misc.ProxyGenerator,呼叫 generateProxyClass方法產生binary data,然後寫入檔案,最後通過反編譯工具來檢視內部實現原理。
import dynamic.proxy.UserService;
import java.lang.reflect.*;
public final class $Proxy11 extends Proxy
implements UserService
{
// 構造方法,引數就是剛才傳過來的MyInvocationHandler類的例項
public $Proxy11(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
/**
* 這個方法是關鍵部分
*/
public final void add()
{
try
{
// 實際上就是呼叫MyInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二個問題就解決了
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
// 在靜態程式碼塊中獲取了4個方法:Object中的equals方法、UserService中的add方法、Object中的hashCode方法、Object中toString方法
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("dynamic.proxy.UserService").getMethod("add", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
美中不足:誠然,Proxy 已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支援 interface 代理的桎梏,因為它的設計註定了這個遺憾。回想一下那些動態生成的代理類的繼承關係圖,它們已經註定有一個共同的父類叫 Proxy。Java 的繼承機制註定了這些動態代理類們無法實現對 class 的動態代理,原因是多繼承在 Java 中本質上就行不通。
有很多條理由,人們可以否定對 class 代理的必要性,但是同樣有一些理由,相信支援 class 動態代理會更美好。介面和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細化。如果只從方法的宣告及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將因為沒有實現任何介面而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。
但是,不完美並不等於不偉大,偉大是一種本質,Java 動態代理就是佐例。
參考來源: