設(shè)計(jì)模式——代理模式

在閻宏博士的《JAVA與模式》一書中開(kāi)頭是這樣描述代理(Proxy)模式的:代理模式是對(duì)象的結(jié)構(gòu)模式。代理模式給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)原對(duì)象的引用。

在代理模式(Proxy Pattern)中,由于客戶端無(wú)法直接或者不想直接引用或使用一個(gè)對(duì)象,所以通過(guò)“中間件”起到代理目標(biāo)對(duì)象功能的作用,為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。

代理模式的角色:

  • 抽象主題類(Subject):該類或接口負(fù)責(zé)定義具體目標(biāo)類和代理類之間的共同接口方法;
  • 具體主題類(ConcreteSubject):該類也稱為被代理類或被委托類,定義了一個(gè)真實(shí)的對(duì)象,最終執(zhí)行代理類的代理方法中的業(yè)務(wù)邏輯,客戶端通過(guò)代理類間接調(diào)用真實(shí)主題類中定義的方法;
  • 代理類(ProxySubject):該類也被稱為委托類或代理類,該類持有一個(gè)對(duì)真實(shí)主題類的引用,在它的方法中調(diào)用與真實(shí)主題類對(duì)應(yīng)的接口方法;
ProxyPattern.png

Java的三種代理模式

靜態(tài)代理

靜態(tài)代理在使用時(shí),需要定義接口或者父類,被代理對(duì)象與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者是繼承相同父類。

實(shí)例場(chǎng)景

在生活中我們打官司找律師,買房找中介這些場(chǎng)景中都有代理的身影,這里我們以買房子找中介來(lái)模擬代理模式。

抽象主題類和具體主題類

/**
 * 定義代理類和被代理的接口
 * @author Iflytek_dsw
 *
 */
interface IUserSubject {
    public void buyHouse();
}

class NormalUser implements IUserSubject{

    @Override
    public void buyHouse() {
        System.out.println("普通人想買房子");
    }
}

創(chuàng)建客戶端

public class Client {

    /**
     * @param args
     */
    public static void main(String[] args) {
        IUserSubject userSubject = new NormalUser();
        NormalUserProxy proxy = new NormalUserProxy(userSubject);
        proxy.buyHouse();
    }
}

靜態(tài)代理總結(jié):

  1. 有點(diǎn):可以做到在不修改目標(biāo)對(duì)象的功能前提下,對(duì)目標(biāo)功能擴(kuò)展。
  2. 缺點(diǎn):因?yàn)榇韺?duì)象需要與目標(biāo)對(duì)象實(shí)現(xiàn)一樣的接口,所以會(huì)有很多代理類,類太多,同時(shí),一旦接口增加方法,目標(biāo)對(duì)象與代理對(duì)象都要維護(hù).

靜態(tài)代理是最簡(jiǎn)單的代理表現(xiàn)形式,它的UML圖跟裝飾者模式的比較像,但是表達(dá)的含義用途是完全不一樣的:

  1. 裝飾者模式的核心是動(dòng)態(tài)的擴(kuò)展功能,是在Decorator類中持有一個(gè)具體組件的引用,然后動(dòng)態(tài)擴(kuò)展具體組件的功能。
  2. 代理模式是代理具體主題的功能,即具體主題把功能實(shí)現(xiàn)好,代理只是負(fù)責(zé)調(diào)用,不做補(bǔ)充修改。
  3. 客戶端使用都是持有一個(gè)抽象組件,然后指向?qū)?yīng)的裝飾者或代理來(lái)進(jìn)行使用。
動(dòng)態(tài)代理

動(dòng)態(tài)代理是指在運(yùn)行時(shí)動(dòng)態(tài)生成代理類。即代理類的字節(jié)碼將在運(yùn)行時(shí)生成并載入當(dāng)前代理的 ClassLoader。在JDK中通過(guò)Proxy類進(jìn)行實(shí)現(xiàn),Proxy類提供了靜態(tài)的方法用于動(dòng)態(tài)創(chuàng)建代理類和實(shí)例。代理實(shí)例是代理類的一個(gè)實(shí)例。 每個(gè)代理實(shí)例都有一個(gè)關(guān)聯(lián)的調(diào)用處理程序?qū)ο螅磳?shí)現(xiàn)接口 InvocationHandler的自定義實(shí)例對(duì)象。

Proxy類
供用于創(chuàng)建動(dòng)態(tài)代理類和實(shí)例的靜態(tài)方法,它還是由這些方法創(chuàng)建的所有動(dòng)態(tài)代理類的超類。

  • static InvocationHandler getInvocationHandler(Object proxy) :返回指定代理實(shí)例的調(diào)用處理程序。
  • static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces):返回代理類的 java.lang.Class 對(duì)象,并向其提供類加載器和接口數(shù)組。
  • static boolean isProxyClass(Class<?> cl):當(dāng)且僅當(dāng)指定的類通過(guò) getProxyClass 方法或 newProxyInstance 方法動(dòng)態(tài)生成為代理類時(shí),返回 true。
  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):返回一個(gè)指定接口的代理類實(shí)例,該接口可以將方法調(diào)用指派到指定的調(diào)用處理程序。

參數(shù)說(shuō)明

  • ClassLoader loader:指定當(dāng)前目標(biāo)對(duì)象使用類加載器,獲取加載器的方法是固定的
  • Class<?>[] interfaces:目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型
  • InvocationHandler handler:事件處理,執(zhí)行目標(biāo)對(duì)象的方法時(shí),會(huì)觸發(fā)事件處理器的方法,會(huì)把當(dāng)前執(zhí)行目標(biāo)對(duì)象的方法作為參數(shù)傳入

InvocationHandler

每個(gè)代理實(shí)例都具有一個(gè)關(guān)聯(lián)的調(diào)用處理程序。對(duì)代理實(shí)例調(diào)用方法時(shí),將對(duì)方法調(diào)用進(jìn)行編碼并將其指派到它的調(diào)用處理程序的 invoke 方法。

動(dòng)態(tài)代理是不需要定義代理角色的,通過(guò)一個(gè)處理器來(lái)處理代理角色的業(yè)務(wù)邏輯。動(dòng)態(tài)代理有以下特點(diǎn):

  1. 代理對(duì)象,不需要實(shí)現(xiàn)接口
  2. 代理對(duì)象的生成,是利用JDK的API,動(dòng)態(tài)的在內(nèi)存中構(gòu)建代理對(duì)象(需要我們指定創(chuàng)建代理對(duì)象/目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型)
  3. 動(dòng)態(tài)代理也叫做:JDK代理,接口代理

實(shí)例場(chǎng)景

在數(shù)據(jù)庫(kù)連接中,我們都是通過(guò)系統(tǒng)封裝好的Driver類來(lái)進(jìn)行數(shù)據(jù)庫(kù)的操作。這里我以此來(lái)模擬一下。

抽象角色接口類

/**
 * 定義接口
 * @author Iflytek_dsw
 *
 */
interface DBDriver{
    public boolean connect();
    public boolean insert();
    public boolean delete();
}

在抽象角色接口類中,我們定義了對(duì)應(yīng)的角色接口。

實(shí)現(xiàn)接口,具體角色類

/**
 * 數(shù)據(jù)庫(kù)管理
 * @author Iflytek_dsw
 *
 */
class DBDriverManager implements DBDriver{
    
    @Override
    public boolean connect(){
        System.out.println("實(shí)際操作者:連接數(shù)據(jù)庫(kù)");
        return true;
    }
    
    @Override
    public boolean insert(){
        System.out.println("實(shí)際操作者:插入數(shù)據(jù)庫(kù)");
        return true;
    }

    @Override
    public boolean delete() {
        System.out.println("實(shí)際操作者:刪除數(shù)據(jù)庫(kù)");
        return true;
    }
}

代理對(duì)象不需要實(shí)現(xiàn)接口,但是目標(biāo)對(duì)象一定要實(shí)現(xiàn)接口,否則不能用動(dòng)態(tài)代理。動(dòng)態(tài)就體現(xiàn)在用接口來(lái)動(dòng)態(tài)指向上。

定義處理器對(duì)象

class MyinvocationHandler implements InvocationHandler{
    
    private DBDriver dbDriver;
    
    public MyinvocationHandler(DBDriver dbDriver){
        this.dbDriver = dbDriver;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        String methodName = method.getName();
        if("connect".equals(methodName)){
            System.out.println("通過(guò)代理調(diào)用方法:" + methodName);
            dbDriver.connect();
        }else if("insert".equals(methodName)){
            System.out.println("通過(guò)代理調(diào)用方法:" + methodName);
            dbDriver.connect();
        }else if("delete".equals(methodName)){
            System.out.println("通過(guò)代理調(diào)用方法:" + methodName);
            dbDriver.connect();
        }
        return Boolean.TRUE;
    }
}

定義一個(gè)工廠方法用于創(chuàng)建Proxy類

這里有兩種創(chuàng)建方式:

  1. 直接通過(guò)newProxyInstance方法進(jìn)行創(chuàng)建;
  2. 通過(guò)getProxyClass獲取對(duì)應(yīng)的Class對(duì)象,然后在通過(guò)反射獲取對(duì)應(yīng)的實(shí)例,綁定處理器完成。
class DynamicProxyFactory{
    
    public static Object getProxyInstance(Object object){
        
        Class proxy = Proxy.getProxyClass(object.getClass().getClassLoader(), 
                object.getClass().getInterfaces());
        try {
            //通過(guò)反射獲取構(gòu)造函數(shù),然后構(gòu)建處理器
            return proxy.getConstructor(new Class[] { InvocationHandler.class })
            .newInstance(new MyinvocationHandler((DBDriver)object));
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public static Object getProxyInstance2(Object object){
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), 
                object.getClass().getInterfaces(), new MyinvocationHandler((DBDriver)object));
    }
}

客戶端使用

public class Client {

    /**
     * @param args
     */
    public static void main(String[] args) {
        DBDriver dbDriver = new DBDriverManager();
        DBDriver flextProxy = (DBDriver) DynamicProxyFactory.getProxyInstance(dbDriver);
        flextProxy.connect();
        flextProxy.insert();
        System.out.println("簡(jiǎn)化方式\n");
        DBDriver simpleProxy = (DBDriver) DynamicProxyFactory.getProxyInstance2(dbDriver);
        simpleProxy.connect();
        simpleProxy.delete();
        
    }
}

結(jié)果

通過(guò)代理調(diào)用方法:connect
實(shí)際操作者:連接數(shù)據(jù)庫(kù)
通過(guò)代理調(diào)用方法:insert
實(shí)際操作者:連接數(shù)據(jù)庫(kù)
簡(jiǎn)化方式

通過(guò)代理調(diào)用方法:connect
實(shí)際操作者:連接數(shù)據(jù)庫(kù)
通過(guò)代理調(diào)用方法:delete
實(shí)際操作者:連接數(shù)據(jù)庫(kù)

動(dòng)態(tài)代理和靜態(tài)代理對(duì)比

  1. 不需要為真實(shí)主題寫一個(gè)形式上完全一樣的代理類,假如主題接口中的方法很多,為每一個(gè)接口寫一個(gè)代理方法也很麻煩。如果接口有變動(dòng),則真實(shí)主題和代理類都要修改,不利于系統(tǒng)維護(hù)。
  2. 使用一些動(dòng)態(tài)代理的生成方法甚至可以在運(yùn)行時(shí)制定代理類的執(zhí)行邏輯,從而大大提升系統(tǒng)的靈活性。比如過(guò)濾數(shù)據(jù)。

動(dòng)態(tài)代理類使用字節(jié)碼動(dòng)態(tài)生成加載技術(shù),在運(yùn)行時(shí)生成加載類。生成動(dòng)態(tài)代理類的方法很多,如,JDK 自帶的動(dòng)態(tài)處理、CGLIB、Javassist 或者 ASM 庫(kù)。JDK 的動(dòng)態(tài)代理使用簡(jiǎn)單,它內(nèi)置在 JDK 中,因此不需要引入第三方 Jar 包,但相對(duì)功能比較弱。CGLIB 和 Javassist 都是高級(jí)的字節(jié)碼生成庫(kù),總體性能比 JDK 自帶的動(dòng)態(tài)代理好,而且功能十分強(qiáng)大。ASM 是低級(jí)的字節(jié)碼生成工具,使用 ASM 已經(jīng)近乎于在使用 Java bytecode 編程,對(duì)開(kāi)發(fā)人員要求最高,當(dāng)然,也是性能最好的一種動(dòng)態(tài)代理生成工具。但 ASM 的使用很繁瑣,而且性能也沒(méi)有數(shù)量級(jí)的提升,與 CGLIB 等高級(jí)字節(jié)碼生成工具相比,ASM 程序的維護(hù)性較差,如果不是在對(duì)性能有苛刻要求的場(chǎng)合,還是推薦 CGLIB 或者 Javassist。(引用自【周明耀-代理模式原理及實(shí)例講解】)

代理模式的使用場(chǎng)景

遠(yuǎn)程代理
為一個(gè)對(duì)象在不同的地址空間提供局部代表,這樣可以隱藏一個(gè)對(duì)象存在于不同地址空間的事實(shí)。使服務(wù)器端Server實(shí)現(xiàn)對(duì)客戶端的隱藏,以便Server可以忽略服務(wù)器端。

虛擬代理
是根據(jù)需要?jiǎng)?chuàng)建開(kāi)銷很大的對(duì)象,通過(guò)它來(lái)存放實(shí)例化需要很長(zhǎng)時(shí)間的真實(shí)對(duì)象,并在需要的時(shí)候進(jìn)行加載。

保護(hù)代理
通過(guò)代理模式來(lái)控制真實(shí)對(duì)象訪問(wèn)時(shí)的權(quán)限。一般用于對(duì)象應(yīng)該有不同的訪問(wèn)權(quán)限的時(shí)候;

只能引用
在訪問(wèn)原始對(duì)象時(shí)執(zhí)行一些額外的附加操作并對(duì)指向原始數(shù)據(jù)計(jì)數(shù)。

總結(jié)

設(shè)計(jì)模式是前人的工作總結(jié)經(jīng)驗(yàn),靈活運(yùn)用,針對(duì)某一類型的教優(yōu)解決方案。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、概述 ??代理模式我們接觸的就比較多了,所謂的代理模式就是,給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)原...
    騎著烏龜去看海閱讀 1,002評(píng)論 0 9
  • 1. 定義 為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。 2. 使用場(chǎng)景 當(dāng)想對(duì)某個(gè)對(duì)象做功能增強(qiáng)拓展,但又不想...
    程序員修仙閱讀 442評(píng)論 1 2
  • 目錄 本文的結(jié)構(gòu)如下: 引言 什么是代理模式 模式的結(jié)構(gòu) 典型代碼 代理模式分類 代碼示例 代理模式和裝飾者模式的...
    w1992wishes閱讀 1,638評(píng)論 0 13
  • 題記:偶讀人民網(wǎng)《城區(qū)道路以克論凈…辭退環(huán)衛(wèi)工》一文,有感,乃為之文。 主題:《既以克論地凈否,以斤看官是否貪?》...
    河邊茅草閱讀 190評(píng)論 0 1
  • 我是容易著急上火發(fā)脾氣的類型,并且屬于那種往往事后后悔型??催^(guò)很多心靈雞湯類的文章,教如何控制自己的脾氣,發(fā)現(xiàn)事...
    湘心靈聽(tīng)閱讀 346評(píng)論 0 0

友情鏈接更多精彩內(nèi)容