程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> JAVA設計模式-代理模式

JAVA設計模式-代理模式

編輯:JAVA綜合教程

JAVA設計模式-代理模式


代理模式

代理模式使用代理對象完成用戶請求,屏蔽用戶對真實對象的訪問。現實世界的代理人被授權執行當事人的一些事宜,無需當事人出面,從第三方的角度看,似乎當事人並不存在,因為他只和代理人通信。而事實上代理人是要有當事人的授權,並且在核心問題上還需要請示當事人。

在軟件設計中,使用代理模式的意圖也很多,比如因為安全原因需要屏蔽客戶端直接訪問真實對象,或者在遠程調用中需要使用代理類處理遠程方法調用的技術細節 (如 RMI),也可能為了提升系統性能,對真實對象進行封裝,從而達到延遲加載的目的。
代理模式角色分為 4 種:

主題接口:定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法 真實主題:真正實現業務邏輯的類; 代理類:用來代理和封裝真實主題; Main:客戶端,使用代理類和主題接口完成一些工作。

延遲加載

以一個簡單的示例來闡述使用代理模式實現延遲加載的方法及其意義。假設某客戶端軟件有根據用戶請求去數據庫查詢數據的功能。在查詢數據前,需要獲得數據庫連接,軟件開啟時初始化系統的所有類,此時嘗試獲得數據庫連接。當系統有大量的類似操作存在時 (比如 XML 解析等),所有這些初始化操作的疊加會使得系統的啟動速度變得非常緩慢。為此,使用代理模式的代理類封裝對數據庫查詢中的初始化操作,當系統啟動時,初始化這個代理類,而非真實的數據庫查詢類,而代理類什麼都沒有做。因此,它的構造是相當迅速的。

在系統啟動時,將消耗資源最多的方法都使用代理模式分離,可以加快系統的啟動速度,減少用戶的等待時間。而在用戶真正做查詢操作時再由代理類單獨去加載真實的數據庫查詢類,完成用戶的請求。這個過程就是使用代理模式實現了延遲加載。

延遲加載的核心思想是:如果當前並沒有使用這個組件,則不需要真正地初始化它,使用一個代理對象替代它的原有的位置,只要在真正需要的時候才對它進行加載。使用代理模式的延遲加載是非常有意義的,首先,它可以在時間軸上分散系統壓力,尤其在系統啟動時,不必完成所有的初始化工作,從而加速啟動時間;其次,對很多真實主題而言,在軟件啟動直到被關閉的整個過程中,可能根本不會被調用,初始化這些數據無疑是一種資源浪費。例如使用代理類封裝數據庫查詢類後,系統的啟動過程這個例子。若系統不使用代理模式,則在啟動時就要初始化 DBQuery 對象,而使用代理模式後,啟動時只需要初始化一個輕量級的對象 DBQueryProxy。
下面代碼 IDBQuery 是主題接口,定義代理類和真實類需要對外提供的服務,定義了實現數據庫查詢的公共方法 request() 函數。DBQuery 是真實主題,負責實際的業務操作,DBQueryProxy 是 DBQuery 的代理類。
清單 1. 延遲加載代理

public interface IDBQuery {
    String request();
}

public class DBQuery implements IDBQuery{
    public DBQuery(){
        try{
            Thread.sleep(1000);//假設數據庫連接等耗時操作
        }catch(InterruptedException ex){
            ex.printStackTrace();
        }
    }

    @Override
    public String request() {
        // TODO Auto-generated method stub
        return "request string";
    }

}
public class DBQueryProxy implements IDBQuery{
    private DBQuery real = null;

    @Override
    public String request() {
        // TODO Auto-generated method stub
        //在真正需要的時候才能創建真實對象,創建過程可能很慢
        if(real==null){
            real = new DBQuery();
        }//在多線程環境下,這裡返回一個虛假類,類似於 Future 模式
        return real.request();
    }

}
public class Main {
    public static void main(String[] args){
        IDBQuery q = new DBQueryProxy(); //使用代裡
        q.request(); //在真正使用時才創建真實對象
    }
}

動態代理

動態代理是指在運行時動態生成代理類。即,代理類的字節碼將在運行時生成並載入當前代理的 ClassLoader。與靜態處理類相比,動態類有諸多好處。首先,不需要為真實主題寫一個形式上完全一樣的封裝類,假如主題接口中的方法很多,為每一個接口寫一個代理方法也很麻煩。如果接口有變動,則真實主題和代理類都要修改,不利於系統維護;其次,使用一些動態代理的生成方法甚至可以在運行時制定代理類的執行邏輯,從而大大提升系統的靈活性。

動態代理類使用字節碼動態生成加載技術,在運行時生成加載類。生成動態代理類的方法很多,如,JDK 自帶的動態處理、CGLIB、Javassist 或者 ASM 庫。JDK 的動態代理使用簡單,它內置在 JDK 中,因此不需要引入第三方 Jar 包,但相對功能比較弱。CGLIB 和 Javassist 都是高級的字節碼生成庫,總體性能比 JDK 自帶的動態代理好,而且功能十分強大。ASM 是低級的字節碼生成工具,使用 ASM 已經近乎於在使用 Java bytecode 編程,對開發人員要求最高,當然,也是性能最好的一種動態代理生成工具。但 ASM 的使用很繁瑣,而且性能也沒有數量級的提升,與 CGLIB 等高級字節碼生成工具相比,ASM 程序的維護性較差,如果不是在對性能有苛刻要求的場合,還是推薦 CGLIB 或者 Javassist。

以清單 1 所示代碼中的 DBQueryProxy 為例,使用動態代理生成動態類,替換上例中的 DBQueryProxy。首先,使用 JDK 的動態代理生成代理對象。JDK 的動態代理需要實現一個處理方法調用的 Handler,用於實現代理方法的內部邏輯。

清單 2. 動態代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DBQueryHandler implements InvocationHandler{
    IDBQuery realQuery = null;//定義主題接口

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
        // TODO Auto-generated method stub
        //如果第一次調用,生成真實主題
        if(realQuery == null){
            realQuery = new DBQuery();
        }
        //返回真實主題完成實際的操作
        return realQuery.request();
    }

}

以上代碼實現了一個 Handler,可以看到,它的內部邏輯和 DBQueryProxy 是類似的。在調用真實主題的方法前,先嘗試生成真實主題對象。接著,需要使用這個 Handler 生成動態代理對象。代碼如清單 3 所示。

清單 3. 生成動態代理對象

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class DBQueryHandler implements InvocationHandler{
    IDBQuery realQuery = null;//定義主題接口

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
        // TODO Auto-generated method stub
        //如果第一次調用,生成真實主題
        if(realQuery == null){
            realQuery = new DBQuery();
        }
        //返回真實主題完成實際的操作
        return realQuery.request();
    }

    public static IDBQuery createProxy(){
        IDBQuery proxy = (IDBQuery)Proxy.newProxyInstance(
        ClassLoader.getSystemClassLoader(), new Class[]{IDBQuery.class}, new DBQueryHandler()
        );
        return proxy;
    }

}

以上代碼生成了一個實現了 IDBQuery 接口的代理類,代理類的內部邏輯由 DBQueryHandler 決定。生成代理類後,由 newProxyInstance() 方法返回該代理類的一個實例。至此,一個完整的動態代理完成了。

在 Java 中,動態代理類的生成主要涉及對 ClassLoader 的使用。以 CGLIB 為例,使用 CGLIB 生成動態代理,首先需要生成 Enhancer 類實例,並指定用於處理代理業務的回調類。在 Enhancer.create() 方法中,會使用 DefaultGeneratorStrategy.Generate() 方法生成動態代理類的字節碼,並保存在 byte 數組中。接著使用 ReflectUtils.defineClass() 方法,通過反射,調用 ClassLoader.defineClass() 方法,將字節碼裝載到 ClassLoader 中,完成類的加載。最後使用 ReflectUtils.newInstance() 方法,通過反射,生成動態類的實例,並返回該實例。基本流程是根據指定的回調類生成 Class 字節碼—通過 defineClass() 將字節碼定義為類—使用反射機制生成該類的實例。從清單 4 到清單 7 所示是使用 CGLIB 動態反射生成類的完整過程。

清單 4. 定義接口

public interface BookProxy {
public void addBook();
}

清單 5. 定義實現類

//該類並沒有申明 BookProxy 接口
public class BookProxyImpl {
    public void addBook() { 
        System.out.println("增加圖書的普通方法..."); 
    } 
}

清單 6. 定義反射類及重載方法
import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer; 
import net.sf.cglib.proxy.MethodInterceptor; 
import net.sf.cglib.proxy.MethodProxy; 

public class BookProxyLib implements MethodInterceptor {
private Object target; 
/** 
 * 創建代理對象 
 * 
 * @param target 
 * @return 
*/ 
public Object getInstance(Object target) { 
 this.target = target; 
 Enhancer enhancer = new Enhancer(); 
 enhancer.setSuperclass(this.target.getClass()); 
 // 回調方法 
 enhancer.setCallback(this); 
 // 創建代理對象 
 return enhancer.create(); 
} 

@Override 
// 回調方法 
public Object intercept(Object obj, Method method, Object[] args, 
 MethodProxy proxy) throws Throwable { 
 System.out.println("事物開始"); 
 proxy.invokeSuper(obj, args); 
 System.out.println("事物結束"); 
 return null; 
} 
}

清單 7. 運行程序

public class TestCglib { 
public static void main(String[] args) { 
BookProxyLib cglib=new BookProxyLib(); 
BookProxyImpl bookCglib=(BookProxyImpl)cglib.getInstance(new BookProxyImpl()); 
bookCglib.addBook(); 
} 
}

清單 8. 運行輸出
事物開始
增加圖書的普通方法…
事物結束

代理模式的應用場合
代理模式有多種應用場合,如下所述:

遠程代理,也就是為一個對象在不同的地址空間提供局部代表,這樣可以隱藏一個對象存在於不同地址空間的事實。比如說 WebService,當我們在應用程序的項目中加入一個 Web 引用,引用一個 WebService,此時會在項目中聲稱一個 WebReference 的文件夾和一些文件,這個就是起代理作用的,這樣可以讓那個客戶端程序調用代理解決遠程訪問的問題;

虛擬代理,是根據需要創建開銷很大的對象,通過它來存放實例化需要很長時間的真實對象。這樣就可以達到性能的最優化,比如打開一個網頁,這個網頁裡面包含了大量的文字和圖片,但我們可以很快看到文字,但是圖片卻是一張一張地下載後才能看到,那些未打開的圖片框,就是通過虛擬代裡來替換了真實的圖片,此時代理存儲了真實圖片的路徑和尺寸;

安全代理,用來控制真實對象訪問時的權限。一般用於對象應該有不同的訪問權限的時候;

指針引用,是指當調用真實的對象時,代理處理另外一些事。比如計算真實對象的引用次數,這樣當該對象沒有引用時,可以自動釋放它,或當第一次引用一個持久對象時,將它裝入內存,或是在訪問一個實際對象前,檢查是否已經釋放它,以確保其他對象不能改變它。這些都是通過代理在訪問一個對象時附加一些內務處理;

延遲加載,用代理模式實現延遲加載的一個經典應用就在 Hibernate 框架裡面。當 Hibernate 加載實體 bean 時,並不會一次性將數據庫所有的數據都裝載。默認情況下,它會采取延遲加載的機制,以提高系統的性能。Hibernate 中的延遲加載主要分為屬性的延遲加載和關聯表的延時加載兩類。實現原理是使用代理攔截原有的 getter 方法,在真正使用對象數據時才去數據庫或者其他第三方組件加載實際的數據,從而提升系統性能。

比如偉大的Spring框架中的AOP就是利用了動態代理來實現

結束語
設計模式是前人工作的總結和提煉。通常,被人們廣泛流傳的設計模式都是對某一特定問題的成熟的解決方案。如果能合理地使用設計模式,不僅能使系統更容易地被他人理解,同時也能使系統擁有更加合理的結構。本文對代理模式的 4 種角色、延遲加載、動態代理等做了一些介紹,希望能夠幫助讀者對代理模式有進一步的了解。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved