Java設計模式學習06——靜態代理與動態代理

漢森X發表於2016-10-30

一、代理模式


為某個物件提供一個代理,從而控制這個代理的訪問。代理類和委託類具有共同的父類或父介面,這樣在任何使用委託類物件的地方都可以使用代理類物件替代。代理類負責請求的預處理、過濾、將請求分配給委託類處理、以及委託類處理完請求的後續處理。

二、代理模式結構


UML類圖:

這裡寫圖片描述

由上圖代理模式的結構為:

  • 抽象角色: 真實物件和代理物件的共同介面。
  • 代理角色: 代理物件角色內部含有對真實物件的引用,從而可以操作真實物件,同時代理物件提供與真實物件相同的介面以便在任何時刻都能代替真實物件。同時,代理物件可以在執行真實物件操作時,附加其他的操作,相當於對真實物件進行封裝。
  • 真實角色: 代理角色所代表的真實物件,是我們最終要引用的物件。

根據代理類的生成時間不同可以將代理分為靜態代理和動態代理兩種。

三、靜態代理


由程式設計師建立或工具生成代理類的原始碼,再編譯代理類。所謂靜態也就是在程式執行前就已經存在代理類的位元組碼檔案,代理類和委託類的關係在執行前就確定了。
示例程式碼:
1.抽象角色

public interface AbstractSubject {
     void doSomething();
}   

2.代理角色

public class ProxySubject implements AbstractSubject{
     private AbstractSubject  real ;
     public ProxySubject(AbstractSubject  real) {
         this.real=real ;
    }
     @Override
     public void doSomething() {

         real.doSomething();
    }

     public void doOtherthing() {

    }
}   

3.真實角色

public class RealSubject implements AbstractSubject{
     @Override
     public void doSomething() {
        System.out.println( "真實角色被使用" );
    }
}   

4.客戶端

public class Client {
     public static void main(String[]  args ) {
        RealSubject real = new  RealSubject();
        ProxySubject proxy = new  ProxySubject( real );
         proxy.doSomething();
    }
}   

5.靜態代理的優缺點

優點: 業務類只需要關注業務邏輯本身,保證了業務類的重用性。這是代理的共有優點。
缺點:

  • 代理物件的一個介面只服務於一種型別的物件,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程式規模稍大時就無法勝任了。
  • 如果介面增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度。

四、動態代理


動態代理類的原始碼是程式在執行期間由JVM根據反射等機制動態生成的,所以不存在代理類的位元組碼檔案。代理角色和真實角色的聯絡在程式執行時確定。
1.首先看看和動態代理相關JavaAPI
① .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 )   

②.java.lang.reflect.InvocationHandler
這是呼叫處理器介面,它自定義了一個 invoke 方法,用於集中處理在動態代理類物件上的方法呼叫,通常在該方法中實現對委託類的代理訪問。每次生成動態代理類物件時都要指定一個對應的呼叫處理器物件。
InvocationHandler核心方法

//該方法負責集中處理動態代理類上的所有方法呼叫。第一個引數既是代理類例項,第二個引數是被呼叫的方法物件  
//第三個方法是呼叫引數。呼叫處理器根據這三個引數進行預處理或分派到委託類例項上反射執行   
Object invoke(Object proxy, Method  method,Object[] args )     

③ .java.lang.reflect.ClassLoader
  這是類裝載器類,負責將類的位元組碼裝載到 Java 虛擬機器(JVM)中併為其定義類物件,然後該類才能被使用。Proxy 靜態方法生成動態代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區別就是其位元組碼是由 JVM 在執行時動態生成的而非預存在於任何一個 .class 檔案中。每次生成動態代理類物件時都需要指定一個類裝載器物件 。

2.動態代理實現步驟

  • 實現InvocationHandler介面建立自己的呼叫處理器 。
  • 給Proxy類提供ClassLoader和代理介面型別陣列建立動態代理類 。
  • 執行真實角色具體任務。

示例程式碼

1.抽象角色和真實角色程式碼與上述相同 。
2. 建立自己的呼叫處理器:

public class SubjectHandler implements InvocationHandler{
    AbstractSubject real;
    public SubjectHandler(AbstractSubject real){
        this.real=real;
    }

    @Override
    public Object invoke(Object obj, Method method, Object[] args)throws Throwable {
        System.out.println("代理類預處理任務");
        //利用反射機制將請求分派給委託類處理。Method的invoke返回Object物件作為方法執行結果。  
  //因為示例程式沒有返回值,所以這裡忽略了返回值處理
        method.invoke(real, args);
        System.out.println("代理類後續處理任務");
        return null;
    }

}

3.客戶端 :

public class Client {

    public static void main(String[] args) {
        RealSubject real=new RealSubject();
        SubjectHandler handler=new SubjectHandler(real);
        //生成代理類物件
        AbstractSubject proxy=(AbstractSubject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{AbstractSubject.class},handler);
        proxy.doSomething();

    }

}

4.動態代理的優缺點

優點
  動態代理與靜態代理相比較,最大的好處是介面中宣告的所有方法都被轉移到呼叫處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在介面方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。

不足
  誠然,Proxy 已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支援 interface 代理的桎梏,因為它的設計註定了這個遺憾。回想一下那些動態生成的代理類的繼承關係圖,它們已經註定有一個共同的父類叫 Proxy。Java 的繼承機制註定了這些動態代理類們無法實現對 class 的動態代理,原因是多繼承在 Java 中本質上就行不通。

本文動態代理部分內容大量引自:http://layznet.iteye.com/blog/1182924

相關文章