一、代理概念 為某個對象提供一個代理,以控制對這個對象的訪問。 代理類和委托類有共同的父類或父接口,這樣在任何使用委托類對象的地方都可以用代理對象替代。代理類負責請求的預處理、過濾、將請求分派給委托類處理、以及委托類執行完請求后的后續處理。 圖1:代理模式
二、靜態代理 由程序員創建或工具生成代理類的源碼,再編譯代理類。所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委托類的關系在運行前就確定了。
清單1:代理接口
/**
* 代理接口。處理給定名字的任務。
*/
public interface Subject {
/**
* 執行給定名字的任務。
* @param taskName 任務名
*/
public void dealTask(String taskName);
}
清單2:委托類,具體處理業務
/**
* 真正執行任務的類,實現了代理接口。
*/
public class RealSubject implements Subject {
/**
* 執行給定名字的任務。這里打印出任務名,并休眠500ms模擬任務執行了很長時間
* @param taskName
*/
@Override
public void dealTask(String taskName) {
System.out.println("正在執行任務:"+taskName);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
清單3:靜態代理類
/**
* 代理類,實現了代理接口。
*/
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)+"毫秒");
}
}
清單4:生成靜態代理類工廠
public class SubjectStaticFactory {
//客戶類調用此工廠方法獲得代理對象。
//對客戶類來說,其并不知道返回的是代理類對象還是委托類對象。
public static Subject getInstance(){
return new ProxySubject(new RealSubject());
}
}
清單5:客戶類
public class Client1 {
public static void main(String[] args) {
Subject proxy = SubjectStaticFactory.getInstance();
proxy.dealTask("DBQueryTask");
}
}
靜態代理類優缺點
優點:
業務類只需要關注業務邏輯本身,保證了業務類的重用性。這是代理的共有優點。
缺點:
1)代理對象的一個接口只服務于一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。
2)如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。
三、動態代理 動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,所以不存在代理類的字節碼文件。代理類和委托類的關系是在程序運行時確定。 1、先看看與動態代理緊密關聯的Java API。
1)java.lang.reflect.Proxy 這是 Java 動態代理機制生成的所有動態代理類的父類,它提供了一組靜態方法來為一組接口動態地生成代理類及其對象。
清單6: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)
2)java.lang.reflect.InvocationHandler 這是調用處理器接口,它自定義了一個 invoke 方法,用于集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委托類的代理訪問。每次生成動態代理類對象時都要指定一個對應的調用處理器對象。
清單7:InvocationHandler的核心方法
// 該方法負責集中處理動態代理類上的所有方法調用。第一個參數既是代理類實例,第二個參數是被調用的方法對象
// 第三個方法是調用參數。調用處理器根據這三個參數進行預處理或分派到委托類實例上反射執行
Object invoke(Object proxy, Method method, Object[] args)
3)java.lang.ClassLoader 這是類裝載器類,負責將類的字節碼裝載到 Java 虛擬機(JVM)中并為其定義類對象,然后該類才能被使用。Proxy 靜態方法生成動態代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區別就是其字節碼是由 JVM 在運行時動態生成的而非預存在于任何一個 .class 文件中。 每次生成動態代理類對象時都需要指定一個類裝載器對象 2、動態代理實現步驟 具體步驟是: a. 實現InvocationHandler接口創建自己的調用處理器 b. 給Proxy類提供ClassLoader和代理接口類型數組創建動態代理類 c. 以調用處理器類型為參數,利用反射機制得到動態代理類的構造函數 d. 以調用處理器對象為參數,利用動態代理類的構造函數創建動態代理類對象
清單8:分步驟實現動態代理
// 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對上面具體步驟的后三步做了封裝,簡化了動態代理對象的獲取過程。
清單9:簡化后的動態代理實現
// InvocationHandlerImpl 實現了 InvocationHandler 接口,并能實現方法調用從代理類到委托類的分派轉發
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過 Proxy 直接創建動態代理類實例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class }, handler );
3、動態代理實現示例 ***
清單10:創建自己的調用處理器***
/**
* 動態代理類對應的調用處理程序類
*/
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;
}
}
清單11:生成動態代理對象的工廠,工廠方法列出了如何生成動態代理類對象的步驟。
/**
* 生成動態代理對象的工廠.
*/
public class DynProxyFactory {
//客戶類調用此工廠方法獲得代理對象。
//對客戶類來說,其并不知道返回的是代理類對象還是委托類對象。
public static Subject getInstance(){
Subject delegate = new RealSubject();
InvocationHandler handler = new SubjectInvocationHandler(delegate);
Subject proxy = null;
proxy = (Subject)Proxy.newProxyInstance(
delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(),
handler);
return proxy;
}
}
***清單12:動態代理客戶類***
public class Client {
public static void main(String[] args) {
Subject proxy = DynProxyFactory.getInstance();
proxy.dealTask("DBQueryTask");
}
}
4、動態代理機制特點 首先是動態生成的代理類本身的一些特點。
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)類繼承關系:該類的繼承關系如圖:
5、動態代理的優點和美中不足
優點: 動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。在本示例中看不出來,因為invoke方法體內嵌入了具體的外圍業務(記錄任務處理前后時間并計算時間差),實際中可以類似Spring AOP那樣配置外圍業務。
美中不足: 誠然,Proxy 已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支持 interface 代理的桎梏,因為它的設計注定了這個遺憾。回想一下那些動態生成的代理類的繼承關系圖,它們已經注定有一個共同的父類叫 Proxy。Java 的繼承機制注定了這些動態代理類們無法實現對 class 的動態代理,原因是多繼承在 Java 中本質上就行不通。 有很多條理由,人們可以否定對 class 代理的必要性,但是同樣有一些理由,相信支持 class 動態代理會更美好。接口和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將因為沒有實現任何接口而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。