爲某個對象提供一個代理,以控制對這個對象的訪問。 代理類和委託類有共同的父類或父接口,這樣在任何使用委託類對象的地方均可以用代理對象替代。代理類負責請求的預處理、過濾、將請求分派給委託類處理、以及委託類執行完請求後的後續處理。java
從圖中能夠看出,代理接口(Subject)、代理類(ProxySubject)、委託類(RealSubject)造成一個「品」字結構。react
下面以一個模擬需求說明靜態代理和動態代理:委託類要處理一項耗時較長的任務,客戶類須要打印出執行任務消耗的時間。解決這個問題須要記錄任務執行前時間和任務執行後時間,兩個時間差就是任務執行消耗的時間面試
由咱們本身建立或工具生成代理類的源碼,再編譯代理類。所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就肯定了react-native
1:代理接口數組
/**
* 代理類,實現了代理接口。
*/
public class ProxySubject implements Subject {
//代理類持有一個委託類的對象引用
private Subject delegate;
public ProxySubject(Subject delegate) {
this.delegate = delegate;
}
/**
* 將請求分派給委託類執行,記錄任務執行先後的時間,時間差即爲任務的處理時間
*
* @param taskName
*/
@Override
public void dealTask(String taskName) {
long stime = System.currentTimeMillis();
//將請求分派給委託類處理
delegate.dealTask(taskName);
long ftime = System.currentTimeMillis();
System.out.println("執行任務耗時"+(ftime - stime)+"毫秒");
}
}
複製代碼
清單3:靜態代理類安全
public class SubjectStaticFactory {
//客戶類調用此工廠方法得到代理對象。
//對客戶類來講,其並不知道返回的是代理類對象仍是委託類對象。
public static Subject getInstance(){
return new ProxySubject(new RealSubject());
}
}
複製代碼
public class SubjectStaticFactory {
//客戶類調用此工廠方法得到代理對象。
//對客戶類來講,其並不知道返回的是代理類對象仍是委託類對象。
public static Subject getInstance(){
return new ProxySubject(new RealSubject());
}
}
複製代碼
public class Client1 {
public static void main(String[] args) {
Subject proxy = SubjectStaticFactory.getInstance();
proxy.dealTask("DBQueryTask");
}
}
複製代碼
優勢:業務類只須要關注業務邏輯自己,保證了業務類的重用性。這是代理的共有優勢。 缺點: 1)代理對象的一個接口只服務於一種類型的對象,若是要代理的方法不少,勢必要爲每一種方法都進行代理,靜態代理在程序規模稍大時就沒法勝任了。 2)若是接口增長一個方法,除了全部實現類須要實現這個方法外,全部代理類也須要實現此方法。增長了代碼維護的複雜度。bash
動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,因此不存在代理類的字節碼文件。代理類和委託類的關係是在程序運行時肯定。微信
一、先看看與動態代理緊密關聯的Java API。 1)java.lang.reflect.Proxy 這是 Java 動態代理機制生成的全部動態代理類的父類,它提供了一組靜態方法來爲一組接口動態地生成代理類及其對象。ide
// 方法 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)
複製代碼
java.lang.reflect.InvocationHandler 這是調用處理器接口,它自定義了一個 invoke 方法,用於集中處理在動態代理類對象上的方法調用,一般在該方法中實現對委託類的代理訪問。每次生成動態代理類對象時都要指定一個對應的調用處理器對象。函數
// 該方法負責集中處理動態代理類上的全部方法調用。第一個參數既是代理類實例,第二個參數是被調用的方法對象
// 第三個方法是調用參數。調用處理器根據這三個參數進行預處理或分派到委託類實例上反射執行
Object invoke(Object proxy, Method method, Object[] args)
複製代碼
具體步驟是: a. 實現InvocationHandler接口建立本身的調用處理器 b. 給Proxy類提供ClassLoader和代理接口類型數組建立動態代理類 c. 以調用處理器類型爲參數,利用反射機制獲得動態代理類的構造函數 d. 以調用處理器對象爲參數,利用動態代理類的構造函數建立動態代理類對象
// 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對上面具體步驟的後三步作了封裝,簡化了動態代理對象的獲取過程。
/**
* 動態代理類對應的調用處理程序類
*/
public class SubjectInvocationHandler implements InvocationHandler {
//代理類持有一個委託類的對象引用
private Object delegate;
public SubjectInvocationHandler(Object delegate) {
this.delegate = delegate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long stime = System.currentTimeMillis();
//利用反射機制將請求分派給委託類處理。Method的invoke返回Object對象做爲方法執行結果。
//由於示例程序沒有返回值,因此這裏忽略了返回值處理
method.invoke(delegate, args);
long ftime = System.currentTimeMillis();
System.out.println("執行任務耗時"+(ftime - stime)+"毫秒");
return null;
}
}
複製代碼
首先是動態生成的代理類自己的一些特色。 1)包:若是所代理的接口都是 public 的,那麼它將被定義在頂層包(即包路徑爲空),若是所代理的接口中有非 public 的接口(由於接口不能被定義爲 protect 或 private,因此除 public 以外就是默認的 package 訪問級別),那麼它將被定義在該接口所在包(假設代理了 com.ibm.developerworks 包中的某非 public 接口 A,那麼新生成的代理類所在的包就是 com.ibm.developerworks),這樣設計的目的是爲了最大程度的保證動態代理類不會由於包管理的問題而沒法被成功定義並訪問;
2)類修飾符:該代理類具備 final 和 public 修飾符,意味着它能夠被全部的類訪問,可是不能被再度繼承;
3)類名:格式是「$ProxyN」,其中 N 是一個逐一遞增的阿拉伯數字,表明 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,並非每次調用 Proxy 的靜態方法建立動態代理類都會使得 N 值增長,緣由是若是對同一組接口(包括接口排列的順序相同)試圖重複建立動態代理類,它會很聰明地返回先前已經建立好的代理類的類對象,而不會再嘗試去建立一個全新的代理類,這樣能夠節省沒必要要的代碼重複生成,提升了代理類的建立效率。
4)類繼承關係:該類的繼承關係如圖:
由圖可見,Proxy 類是它的父類,這個規則適用於全部由 Proxy 建立的動態代理類。並且該類還實現了其所代理的一組接口,這就是爲何它可以被安全地類型轉換到其所代理的某接口的根本緣由。
接下來讓咱們瞭解一下代理類實例的一些特色。每一個實例都會關聯一個調用處理器對象,能夠經過 Proxy 提供的靜態方法 getInvocationHandler 去得到代理類實例的調用處理器對象。在代理類實例上調用其代理的接口中所聲明的方法時,這些方法最終都會由調用處理器的 invoke 方法執行,此外,值得注意的是,代理類的根類 java.lang.Object 中有三個方法也一樣會被分派到調用處理器的 invoke 方法執行,它們是 hashCode,equals 和 toString,可能的緣由有:一是由於這些方法爲 public 且非 final 類型,可以被代理類覆蓋;二是由於這些方法每每呈現出一個類的某種特徵屬性,具備必定的區分度,因此爲了保證代理類與委託類對外的一致性,這三個方法也應該被分派到委託類執行。當代理的一組接口有重複聲明的方法且該方法被調用時,代理類老是從排在最前面的接口中獲取方法對象並分派給調用處理器,而不管代理類實例是否正在以該接口(或繼承於該接口的某子接口)的形式被外部引用,由於在代理類內部沒法區分其當前的被引用類型。
接着來了解一下被代理的一組接口有哪些特色。首先,要注意不能有重複的接口,以免動態代理類代碼生成時的編譯錯誤。其次,這些接口對於類裝載器必須可見,不然類裝載器將沒法連接它們,將會致使類定義失敗。再次,需被代理的全部非 public 的接口必須在同一個包中,不然代理類生成也會失敗。最後,接口的數目不能超過 65535,這是 JVM 設定的限制。
最後再來了解一下異常處理方面的特色。從調用處理器接口聲明的方法中能夠看到理論上它可以拋出任何類型的異常,由於全部的異常都繼承於 Throwable 接口,但事實是否如此呢?答案是否認的,緣由是咱們必須遵照一個繼承原則:即子類覆蓋父類或實現父接口的方法時,拋出的異常必須在原方法支持的異常列表以內。因此雖然調用處理器理論上講可以,但實際上每每受限制,除非父接口中的方法支持拋 Throwable 異常。那麼若是在 invoke 方法中的確產生了接口方法聲明中不支持的異常,那將如何呢?放心,Java 動態代理類已經爲咱們設計好了解決方法:它將會拋出 UndeclaredThrowableException 異常。這個異常是一個 RuntimeException 類型,因此不會引發編譯錯誤。經過該異常的 getCause 方法,還能夠得到原來那個不受支持的異常對象,以便於錯誤診斷。
動態代理與靜態代理相比較,最大的好處是接口中聲明的全部方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,咱們能夠進行靈活處理,而不須要像靜態代理那樣每個方法進行中轉。在本示例中看不出來,由於invoke方法體內嵌入了具體的外圍業務(記錄任務處理先後時間並計算時間差),實際中能夠相似
誠然,Proxy 已經設計得很是優美,可是仍是有一點點小小的遺憾之處,那就是它始終沒法擺脫僅支持 interface 代理的桎梏,由於它的設計註定了這個遺憾。回想一下那些動態生成的代理類的繼承關係圖,它們已經註定有一個共同的父類叫 Proxy。Java 的繼承機制註定了這些動態代理類們沒法實現對 class 的動態代理,緣由是多繼承在 Java 中本質上就行不通。
有不少條理由,人們能夠否認對 class 代理的必要性,可是一樣有一些理由,相信支持 class 動態代理會更美好。接口和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細化。若是隻從方法的聲明及是否被定義來考量,有一種二者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將由於沒有實現任何接口而今後與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。
微信公衆號:終端研發部