代理模式與java動態代理

定義

    代理(Proxy)是一種設計模式,提供了對目標對象另外的訪問方式;即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能。有點類似於裝飾者模式。但是與裝飾者模式的區別是代理模式最終不一定調用目標對象的目標方法。但裝飾者一定會。

圖示:


理解

     通俗一點說,就是代理對象持有目標對象的引用,並對外提供所有目標對象的業務方法,在該方法中添加一些邏輯處理並決定是否調用目標對象方法。代理模式的關鍵點是:代理對象與目標對象.代理對象是對目標對象的擴展,並會根據情況調用目標對象。代理模式又分爲靜態代理和動態代理。下面我們分別做介紹。

靜態代理

角色:

1、抽象角色:接口
2、真實角色:真正業務對象
3、代理角色:代理對象

靜態代理的內容比較簡單,這裏我們先借鑑網上的一個很貼切的例子來說明,然後通過例子中總結一下。

實例

在開發中有這樣三種角色能夠很好的類比代理模式。
用戶:提出需求的人 調用者
產品經理:把控需求的人 代理對象
開發人員:實現需求的人 真實角色
1、定義開發人員接口,它有一個實現用戶需求的方法
public interface ICoder {
    public void implDemands(String demandName);

}

2、定義真實角色類:開發人員

public class JavaCoder implements ICoder{

    private String name;

    public JavaCoder(String name){
        this.name = name;
    }

    @Override
    public void implDemands(String demandName) {
        System.out.println(name + " implemented demand:" + demandName + " in JAVA!");
    }
3、代理角色,其中持有真實角色對象。並同時實現開發接口,在實現方法中調用真實角色的業務方法,當然在調用前後會擴展邏輯,要不然豈不是白代理了。這裏並沒有添加業務邏輯,下面的代碼會加。
public class CoderProxy implements ICoder{

    private ICoder coder;

    public CoderProxy(ICoder coder){
        this.coder = coder;
    }

    @Override
    public void implDemands(String demandName) {
        coder.implDemands(demandName);
    }

}
4、爲代理的方法添加額外的業務邏輯
產品經理不僅僅只能調用真實角色的業務方法,肯定要擴展自己的邏輯。例如現在決定不接受新功能的增加,只處理bug。那麼我們可以把代理類做一些修改。
public class CoderProxy implements ICoder{

    private ICoder coder;

    public CoderProxy(ICoder coder){
        this.coder = coder;
    }

    @Override
    public void implDemands(String demandName) {
        if(demandName.startsWith("Add")){
            System.out.println("No longer receive 'Add' demand");
            return;
        }
        coder.implDemands(demandName);
    }
}
這樣就在代理這一層過濾掉了新增加功能的要求(擴展邏輯),而不是修改真實角色的邏輯。這樣既擴展了邏輯也保證了業務方法的獨立性和純潔性。

4、測試

public class Customer {

    public static void main(String args[]){
        //定義一個java碼農
        ICoder coder = new JavaCoder("Zhang");
        //定義一個產品經理
        ICoder proxy = new CoderProxy(coder);
        //讓產品經理實現一個需求
        proxy.implDemands();
    }
}
我們對上面的事例做一個簡單的抽象:

代理模式包含如下角色:
  • Subject:抽象主題角色。可以是接口,也可以是抽象類。
  • RealSubject:真實主題角色。業務邏輯的具體執行者。
  • ProxySubject:代理主題角色。內部含有RealSubject的引用,負責對真實角色的調用,並在真實主題角色處理前後做預處理和善後工作。
代理模式優點:
  • 職責清晰 真實角色只需關注業務邏輯的實現,非業務邏輯部分,後期通過代理類完成即可。
  • 高擴展性 不管真實角色如何變化,由於接口是固定的,代理類無需做任何改動。

動態代理

    假設有這麼一個需求,在方法執行前和執行完成後,打印系統時間。這很簡單嘛,非業務邏輯,只要在代理類調用真實角色的方法前、後輸出時間就可以了。像上例,只有一個implDemands方法,這樣實現沒有問題。但如果真實角色有10個方法,那麼我們要在代理類的每一個代理方法中去寫一遍相同的代碼,這裏我們就可以採用動態代理來解決。
    代理類在程序運行時創建的代理方式被稱爲動態代理。也就是說,代理類並不需要在Java代碼中定義,而是在運行時動態生成的。這樣就可以根據真實角色來動態創建代理類,而不是靜態的創建10個代理類。我們在動態代理中對代理類邏輯做統一處理,而不是修改每個代理類的方法。
與靜態代理相比,抽象角色、真實角色都沒有變化。變化的只有代理類。因此,抽象角色、真實角色,參考ICoder和JavaCoder。
在使用動態代理時,我們需要定義一個位於代理類與委託類(真是角色)之間的中介類,也叫動態代理類,這個類被要求實現InvocationHandler接口
public class CoderDynamicProxy implements InvocationHandler{
     //被代理的實例
    private ICoder coder;

    public CoderDynamicProxy(ICoder _coder){
        this.coder = _coder;
    }

    //調用被代理的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(System.currentTimeMillis());
        Object result = method.invoke(coder, args);
        System.out.println(System.currentTimeMillis());
        return result;
    }
}
當我們調用代理類對象的方法時,這個“調用”會轉送到中介類的invoke方法中,參數method標識了我們具體調用的是代理類的哪個方法,args爲這個方法的參數。此時我們便可以在動態代理對象執行方法前後擴展邏輯,例如這裏添加打印時間。
public class DynamicClient {
 
     public static void main(String args[]){
            //要代理的真實對象
            ICoder coder = new JavaCoder("Zhang");
            //創建中介類實例
            InvocationHandler  handler = new CoderDynamicProxy(coder);
            //獲取類加載器
            ClassLoader cl = coder.getClass().getClassLoader();
            //動態產生一個代理對象
            ICoder proxy = (ICoder) Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler);
            //通過代理類,執行doSomething方法;這裏就走到了CoderDynamicProxy.invoke()中
            proxy.implDemands("Modify user management");
        }
}

執行結果如下:

1501728574978
Zhang implemented demand:Modify user management in JAVA!
1501728574979
    這裏我們重點看下動態產生代理類的方法,通過Proxy.newProxy.newProxyInstance()方法來創建動態代理對象:
  • 第一個參數 handler.getClass().getClassLoader() ,我們這裏使用handler這個類的ClassLoader對象來加載我們的代理對象
  •  第二個參數coder.getClass().getInterfaces(),我們這裏爲代理對象提供的接口是真實對象所實行的接口,表示我要代理的是該真實對象,這樣我就能調用這組接口中的方法了
  •  第三個參數handler, 我們這裏將這個代理對象關聯到了上方的 InvocationHandler 這個對象上
首先,我們解釋一下動態生成的代理類的類型爲什麼可以是ICoder,因爲方法newProxyInstance第二個參數傳入了真實對象所實現的接口。那麼動態生成的代理類就會實現這組接口,那麼生成的代理對象便可以轉化爲這組接口中的任一一個。
另外,通過 Proxy.newProxyInstance 創建的代理對象是在jvm運行時動態生成的一個對象,它並不是我們的InvocationHandler類型,也不是我們定義的那組接口的類型,而是在運行是動態生成的一個對象,並且命名方式都是這樣的形式,以$開頭,proxy爲中,最後一個數字表示對象的標號。

接下來,我們調用動態代理對象的implDemands()方法。這個時候程序就會跳轉到由這個代理對象關聯到的 handler 中的invoke方法去執行,而我們的這個 handler 對象又接受了一個 RealSubject類型的參數,表示我要代理的就是這個真實對象,所以此時就會調用 handler 中的invoke方法去執行。此時通過method.invoke(coder,args)來調用真實對象的業務方法。

總結一下,一個典型的動態代理可分爲以下五個步驟:

1、創建抽象角色

2、創建真實角色

3、通過實現InvocationHandler接口創建中介類

4、通過Proxy.newProxyInstance()生成一個動態代理對象

5、通過代理對象調用業務方法

動態代理的使用基本就是這樣,接下來我們深入JDK看看java是如何做到動態生成一個代理類,代碼又是如何執行到中介類InvocationHandler.invoke()裏面的。

進入Proxy.newProxyInstance()源碼中查看主要邏輯如下:

//生成代理類class,並加載到jvm中,獲取Class對象
Class<?> cl = getProxyClass0(loader, interfaces);
//獲取代理類參數爲InvocationHandler的構造函數
final Constructor<?> cons = cl.getConstructor(constructorParams);
//生成代理類的實例對象,並返回
return newInstance(cons, ih);

我們一步一步分析上述幾行代碼

第一步生成代理類,該例中生成的便是實現ICoder接口的類,並將該類加載到jvm中,進入getProxyClass0()中核心邏輯如下:

 Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(interfaceName, false, loader);
            } catch (ClassNotFoundException e) {
            }

通過Class.forName()生成代理類並加載到jvm中,返回Class對象。Calss.forName()中調用的是native方法

第二步獲取構造函數

我們可以看到constructorParams是Proxy維護的靜態變量,其中包括InvocationHandler,源碼如下:

    /** parameter types of a proxy class constructor */
    private final static Class[] constructorParams =
        { InvocationHandler.class };

第三步通過構造函數創建實例,參數爲構造器和構造時需要使用的中介類InvocationHandler

    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
        try {
            return cons.newInstance(new Object[] {h} );
        } catch (IllegalAccessException | InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString());
            }
        }
    }

    這樣,就解決了我們提出的第一個問題:如何生成代理對象。其實這部分涉及到類加載機制相關的知識,不熟悉的同學可以先學習這部分內容。那麼,爲什麼我們調用代理對象的業務方法,就會執行到中介類InvocationHandler.invoke()方法中呢?這就有從生成的代理類代碼中尋找答案了。我們可以手動模擬生成代理類,如下:

public class CodeUtil {

       public static void main(String[] args) throws IOException {
            byte[] classFile = ProxyGenerator.generateProxyClass("TestProxyGen", JavaCoder.class.getInterfaces());
            File file = new File("D:/aaa/TestProxyGen.class");
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(classFile);
            fos.flush();
            fos.close();
          }
            }

注意這裏的類名TestProxyGen是我們模擬的,真正的類名還是$Proxy0

我們查看生成代理類的代碼,其中,業務方法implDemands如下:


這裏調用了h也就是InvocationHandler的invoke方法,這就能解釋爲何我們調用代理對象的業務方法,最終走到了中介類的invoke()方法中了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章