設計模式之代理模式——Java語言描述
代理模式是一種常見的設計模式,它使用代理物件完成使用者請求,遮蔽使用者對真實物件的訪問
在軟體設計中,使用代理模式的意圖很多,比如處於安全原因遮蔽客戶端直接訪問真實物件;在遠端呼叫中,使用代理類處理遠端方法呼叫的技術細節;提升系統性能,對真實物件進行封裝,達到延遲載入的目的。
靜態代理
在一個客戶端中,有根據使用者請求,去資料庫查詢資料的功能。在資料查詢前,需要獲得資料庫連線。在系統有大量類似操作(比如XML解析)存在時,所有這些初始化操作的疊加,將會導致系統啟動速度非常緩慢。
為此,使用代理類封裝對資料庫查詢的初始化操作,當系統啟動時,初始化這個代理類,而非真實的資料庫查詢類。因為代理類什麼都沒有做,所以它的構造是十分快速的。
延遲載入的核心思想是:如果當前並沒有使用這個元件,則不需要真正地初始化它,使用一個代理物件替代它原有的位置,只有在真正需要的時候,才對它進行載入。
採用延遲載入不僅可以在時間軸上分散系統壓力,而且可以避免載入一些從軟體啟動到關閉的整個過程中都沒有使用的元件。
考慮下面這個靜態代理的實現:
主題介面IDBQuery:
public interface IDBQuery {
String request();
}
DBQuery的實現,這是一個重量級物件,構造會比較慢:
public class DBQuery implements IDBQuery{
public DBQuery(){
//可能包含資料庫連線等耗時操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String request() {
return "request string";
}
}
代理類DBQueryProxy是輕量級物件,建立很快,用它來代替DBQuery的位置:
public class DBQueryProxy implements IDBQuery {
private DBQuery real=null;
@Override
public String request() {
if(real==null)
real=new DBQuery();
return real.request();
}
}
動態代理
動態代理指的是可以在執行時,動態生成代理類。即代理類的位元組碼在執行時生成並且載入當前的類載入器。
和靜態代理相比,動態代理有諸多的好處:
- 無需為真實的主題寫一個形式上完全一樣的封裝類。如果介面有改動,則真實的主題和代理類都需要修改,不利於系統維護。
- 一些動態代理的生成方法甚至可以在執行時指定代理類的執行邏輯,從而提高系統的靈活性。
動態代理有很多生成方法,比如JDK自帶的動態代理、CGLIB、Javassist或者ASM。
他們的區別分別是:
- JDK的動態代理使用簡單,因為它內建在JDK中,所以不用引入第三方Jar包,但相對的功能比較弱。
- CGLIB和Javassist是高階的位元組碼生成庫,總體效能比JD自帶的動態代理好,而且功能十分強大。
- ASM是低階的位元組碼生成工具,是效能最好的一種動態代理生成工具,但是使用過於繁瑣,而且效能也沒有數量級的提升。
考慮下面這個動態代理的實現:
以上個例子中的DBQueryProxy為例,使用動態代理生成動態類,替代上個例子中的DBQueryProxy。使用JDK的動態代理生成物件,它要求實現一個處理方法呼叫的Handler,用於實現代方法的內部邏輯。
public class JdkDbQeuryHandler implements InvocationHandler {
IDBQuery real=null;
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(real==null)
real=new DBQuery();
return real.request();
}
}
以上程式碼實現了一個Handler,它的內部邏輯和DBQueryProxy是類似的。在呼叫真實主題的方法之前,先嚐試生成真實主題物件。接著,需要使用這個Handler生成動態代理物件:
public static IDBQuery createJdkProxy(){
IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[] { IDBQuery.class }, new JdkDbQeuryHandler());
return jdkProxy;
}
以上程式碼生成了一個實現了IDBQuery介面的代理類,代理類的內部邏輯由JdkDbQeuryHandler決定。生成代理類後,由newProxyInstance()方法返回該代理類的一個例項。
就動態代理的方法呼叫效能而言,CGLIB和Javassist的基於動態程式碼的代理都優於JDK自帶的動態代理。此外,JDK的動態代理要求代理類和真實主題都實現同一個介面,但是CGLIB和Javassist沒有這個強制要求。