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

Java設計模式之代理初探

編輯:JAVA綜合教程

Java設計模式之代理初探


java設計模式之靜態代理,動態代理
代理模式是對象的結構模式。代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。意思就是一個體或者機構代表另一個體或者機構執行一些行為。在一些情況下,一個客戶端不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。

靜態代理

場景:
比如你要買一張飛機票,你的行為是要買機票,買機票可以通過第三方代理平台,比如阿裡,去哪兒,攜程這些平台幫你買,這種平台就充當了代理角色,這樣的話你就不用直接在航空公司買票了。只需要將你行為需求告訴代理角色即可。

代碼
1.創建行為接口

public interface BuyTicketInterface {
    //買票的行為
    void butTicket();

}

2.創建客戶端,客戶端要實現接口中定義的行為,我要買票。然後執行自己的行為。

public class TravelPeople implements BuyTicketInterface {
    //客戶端的行為和需求,是這樣的。
    public void butTicket() {
        System.out.println("買一張5月1號重慶江北機場飛往北京首都機場的航班,時間14:00到24:00之間");
    }
}

3.創建代理端功能,代理端也要實現買票的行為,不然怎麼完成客戶端的要求,代理執行買票的過程是替將客戶端的執行的。代碼如下。

public class BuyTicketProxy implements BuyTicketInterface{

    private TravelPeople travelPeople;
    //
    public BuyTicketProxy(){
    this.travelPeople = new TravelPeople(); 
    }
    /**
     * 執行的時候調用的是代理的行為,代理是替客戶端辦事的。
     */
    public void buyTicket() {
        if(travelPeople != null){
            travelPeople.buyTicket();
        }
    }
}

4.使用代理來購票。終端行為和代理方處理,實現購片的功能;
代碼如下:

public class TicketService {


    public static void main(String[] args) {
        TicketService.startBuyTicket();
    }
    //開始買票,看似交易的是代理和票務中心交易,其實執行的是客戶端的交易。
    public static void startBuyTicket(){
        BuyTicketInterface buyTick = new BuyTicketProxy();
        buyTick.buyTicket();
    }
}

運行結果


買一張5月1號重慶江北機場飛往北京首都機場的航班,時間14:00到24:00之間

靜態代理模式優缺點
優點:業務類只需要關注業務邏輯本身,保證了業務類的重用性。這是代理的共有優點,同時降低了業務邏輯的耦合性。
缺點:
1)代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。
2)如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。

那麼問題來了,如果要按照上述的方式(靜態代理)使用代理模式,那麼真實角色必須是實現已經存在的,並將其作為代理對象的內部屬性。但是實際使用時,一個真實角色必須對應一個代理角色,但如果大量使用會導致類的急劇膨脹;此外,如果事先並不知道真實角色,該如何使用代理呢?這個問題可以通過Java的動態代理類來解決。

動態代理

動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,所以不存在代理類的字節碼文件。代理類和委托類的關系是在程序運行時確定。 通過Java的反射機制來實現。


1. 先看看與動態代理緊密關聯的Java API。


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) 

2)java.lang.reflect.InvocationHandler


這是調用處理器接口,它自定義了一個 invoke 方法,用於集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委托類的代理訪問。每次生成動態代理類對象時都要指定一個對應的調用處理器對象。

InvocationHandlerd代碼清單如下:

// 該方法負責集中處理動態代理類上的所有方法調用。第一個參數既是代理類實例,第二個參數是被調用的方法對象  
// 第三個方法是調用參數。調用處理器根據這三個參數進行預處理或分派到委托類實例上反射執行  
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. 以調用處理器對象為參數,利用動態代理類的構造函數創建動態代理類對象

// 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 }); 

3.動態代理實現示例

創建接口;

public interface BuyTicketInterface {

    void buyTicket();
}

創建動態代理類對應的調用處理程序類


 public class BuyTicketHandler implements InvocationHandler {
     /**
     * 動態代理類對應的調用處理程序類
     */
    private Object orginObject;
    //代理類持有一個委托類的對象引用
    public BuyTicketHandler(Object orginObject){
        this.orginObject = orginObject;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result ; 
        //調用方法之前;
        doBefore();
          //利用反射機制將請求分派給委托類處理。Method的invoke返回Object對象作為方法執行結果。
          //因為示例程序沒有返回值,所以這裡忽略了返回值處理
        result = method.invoke(this.orginObject, args);
        doAfter();
        return result ; 
    }

    private void doBefore(){
        System.out.println("開始買票");
    }


    private void doAfter(){
        System.out.println("調用買票");
    }



}

生成動態代理對象的工廠,工廠方法列出了如何生成動態代理類對象的步驟。

/**
 * 生成動態代理對象的工廠.
 */
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;
 }
}

動態代理客戶類

public class BuyTicketProxy {
    //客戶類調用此工廠方法獲得代理對象。  
     //對客戶類來說,其並不知道返回的是代理類對象還是委托類對象。  
    public static BuyTicketInterface  startBuy(){
        //創建待代理的對象
        BuyTicketInterface buyTicket = new TravelPeople();
        //創建動態代理
        InvocationHandler handler = new BuyTicketHandler(buyTicket);
        //通過反射來獲取對象。
        BuyTicketInterface buy = (BuyTicketInterface)Proxy.newProxyInstance(buyTicket.getClass().getClassLoader(), buyTicket.getClass().getInterfaces(), handler);
        return buy;
    }

}

創建測試;

BuyTicketInterface buy = BuyTicketProxy.startBuy();
        buy.buyTicket();

運行結果:

開始買票
買一張5月1號重慶江北機場飛往北京首都機場的航班,時間14:00到24:00之間
調用買票

總結:首先是動態生成的代理類本身的一些特點。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 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復創建動態代理類,它會很聰明地返回先前已經創建好的代理類的類對象,而不會再嘗試去創建一個全新的代理類,這樣可以節省不必要的代碼重復生成,提高了代理類的創建效率。

優點:
動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。在本示例中看不出來,因為invoke方法體內嵌入了具體的外圍業務(記錄任務處理前後時間並計算時間差)。

缺點:
Proxy 已經設計得很完美了,但是還是有局限,就是不能擺脫 interface 代理的桎梏,因為它的設計注定了這個遺憾。回想一下那些動態生成的代理類的繼承關系圖,它們已經注定有一個共同的父類叫 Proxy。Java 的繼承機制注定了這些動態代理類們無法實現對 class 的動態代理,原因是多繼承在 Java 中本質上就行不通。

有很多條理由,人們可以否定對 class 代理的必要性,但是同樣有一些理由,相信支持 class 動態代理會更美好。接口和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將因為沒有實現任何接口而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。
最後,代理設計模式結合項目自身的情況而定,做到將設計模式的設計功能發揮到最大化。使項目,易擴展,耦合性低。才是前進的方向。

   

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