java代理模式-原來你是這樣的代理

設(shè)計(jì)模式文章陸續(xù)更新

java單例模式
java工廠模式
java狀態(tài)模式

這幾天在看一些框架源碼時(shí)看到了一個(gè)很奇妙的設(shè)計(jì)模式,有種熟悉個(gè)感覺,一時(shí)想不出是什么模式,后面經(jīng)過了解才知道是動(dòng)態(tài)代理,就這樣帶著好奇心學(xué)習(xí)了這個(gè)模式,更深入了解代理會(huì)發(fā)現(xiàn)不僅有靜態(tài)和動(dòng)態(tài),還有很多其他的代理類別,果然興趣是最好的老師,效率不錯(cuò),下面是我一些總結(jié).

一起來體驗(yàn)下,你也會(huì)發(fā)現(xiàn),原來你是這樣的代理.

這里寫圖片描述

什么是代理?

在<大話設(shè)計(jì)模式>中說到,代理模式,為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問.

下面通過一個(gè)例子,說明下.

商家需要搞活動(dòng),請(qǐng)了陳奕迅過來商演唱歌,那么商家需要跟陳奕迅進(jìn)行面談->簽合同->首付款->安排行程->唱歌->收尾款.

  • 在不使用代理的情況下是這樣的.

    這里寫圖片描述

    可以看到陳奕迅好像很忙,除了要唱歌還要做很多的交互,這樣我的愛豆不是要忙死了.

  • 使用代理模式

    這里寫圖片描述

    通過代理人來處理一些瑣碎的事情后,陳奕迅就只要負(fù)責(zé)他獨(dú)有的功能唱歌就行了,這樣間接的訪問對(duì)象,有效的減輕了一些重復(fù)的操作.

代理的核心角色

我們可以將代理模式分為三大角色:

  • 抽象角色
    • 代理角色真實(shí)角色的公共方法都會(huì)在這里定義.
  • 真實(shí)角色
    • 供給代理角色用,這里實(shí)現(xiàn)了抽象角色的業(yè)務(wù)邏輯.
    • 關(guān)注真正的業(yè)務(wù)邏輯
  • 代理角色
    • 真實(shí)角色的代理,根據(jù)真實(shí)角色的業(yè)務(wù)邏輯來實(shí)現(xiàn)
      抽象角色的抽象方法,并可以附件自己的操作.

java中的代理

代理模式(Proxy)是一種結(jié)構(gòu)型設(shè)計(jì)模式,主要解決的問題是:在直接訪問對(duì)象時(shí)帶來的問題.
它也是一種常用的設(shè)計(jì)模式,其目的就是為其他對(duì)象提供一個(gè)代理以控制對(duì)某個(gè)對(duì)象的訪問.

在java中動(dòng)態(tài)代理機(jī)制以巧妙的方式實(shí)現(xiàn)了代理模式的設(shè)計(jì)理念,因此java中也分為動(dòng)態(tài)代理靜態(tài)代理.

  • 靜態(tài)代理(靜態(tài)定義代理類)
  • 動(dòng)態(tài)代理(動(dòng)態(tài)生成代理類)
    • 反射機(jī)制,JDK自帶的動(dòng)態(tài)代理
    • 通過InvocationHandler(處理器接口)
      • 使用invoke方法實(shí)現(xiàn)對(duì)真實(shí)角色的代理訪問.
      • 每次通過Proxy生產(chǎn)代理類對(duì)象時(shí),都要指定的處理器對(duì)象.
    • java動(dòng)態(tài)性這塊可以深入看看,反射與javassist

靜態(tài)代理(StaticProxy)

借用Eason-S大神的類圖,靜態(tài)代理UML類圖

這里寫圖片描述

talk is cheap,根據(jù)上面陳奕迅的案例來寫個(gè)demo

  • 抽象角色
/*
 * 明星和代理人的公共接口
 */
public interface StarInterface {

    // 面談
    void interview();

    // 簽合同
    void signContract();

    // 首付款
    void firstPayment();

    // 安排行程
    void plan();

    // 唱歌
    void sing();

    //收尾款
    void finalPayment();
}
  • 真實(shí)角色
/*
 * 明星實(shí)現(xiàn)類
 */
public class EasonStar implements StarInterface {

    private final String STAR_NAME = "陳奕迅:";

    @Override
    public void sing() {
        System.out.println(STAR_NAME+"唱歌");
    }

    @Override
    public void finalPayment() {
        System.out.println(STAR_NAME+"收尾款");
    }

    @Override
    public void signContract() {
        System.out.println(STAR_NAME+"簽合同");
    }

    @Override
    public void plan() {
        System.out.println(STAR_NAME+"安排行程");
    }

    @Override
    public void interview() {
        System.out.println(STAR_NAME+"面談");

    }

    @Override
    public void firstPayment() {
        System.out.println(STAR_NAME+"預(yù)付款");
    }
}
  • 代理角色
public class ProxyStar implements StarInterface {
    //私有化被代理角色
    private StarInterface star;
    private final String PROXY_NAME = "代理人:";
    
    /*
     * 使用接口的方式來指向真實(shí)角色(多態(tài)特性)
     */
    public ProxyStar(StarInterface star) {
        super();
        this.star = star;
    }

    @Override
    public void sing() {
        // 唱歌是明星的特有方法,代理是沒有的,因此需要明星自己來
        // 調(diào)用陳奕迅的唱歌方法...
        star.sing();
    }

    @Override
    public void finalPayment() {
        System.out.println(PROXY_NAME+"簽尾款");
    }

    @Override
    public void signContract() {
        System.out.println(PROXY_NAME+"簽合同");
    }

    @Override
    public void plan() {
        System.out.println(PROXY_NAME+"安排行程");
    }

    @Override
    public void interview() {
        System.out.println(PROXY_NAME+"面談");
    }

    @Override
    public void firstPayment() {
        System.out.println(PROXY_NAME+"預(yù)付款");
    }
}
  • 客戶端類
public class Client {

    public static void main(String[] args) {
        //找到陳奕迅
        EasonStar realStar = new EasonStar();
        //找到代理人,專門為陳奕迅代理
        ProxyStar proxyStar = new ProxyStar(realStar);
        //代理人來完成
        proxyStar.interview();
        proxyStar.signContract();
        proxyStar.firstPayment();
        proxyStar.plan();
        //這里調(diào)用的是 EasonStar的sing方法
        proxyStar.sing();
        proxyStar.finalPayment();
    }
}

輸出結(jié)果:

代理人:面談
代理人:簽合同
代理人:預(yù)付款
代理人:安排行程
陳奕迅:唱歌
代理人:簽尾款

從上面靜態(tài)代理demo中,你會(huì)發(fā)現(xiàn)無論代理角色真實(shí)角色都需要實(shí)現(xiàn)接口,并且將真實(shí)角色的細(xì)節(jié)向調(diào)用方完全隱藏,可以看下EasonStar類里面有很多方法,但最終被掉用的只有sing(),其他都是代理角色來調(diào)用的.

動(dòng)態(tài)代理(DynamicProxy)

動(dòng)態(tài)代理的思維模式與之前的一般模式是一樣的,也是面向接口進(jìn)行編碼,創(chuàng)建代理類將具體類隱藏解耦,不同之處在于代理類的創(chuàng)建時(shí)機(jī)不同,動(dòng)態(tài)代理需要在運(yùn)行時(shí)因需實(shí)時(shí)創(chuàng)建.

這里寫圖片描述

看下代碼.
由于抽象角色真實(shí)角色的代碼跟上面靜態(tài)代理是一樣的這里就直接給出代理角色和客戶端類的代碼.

  • 代理角色
/**
 * 動(dòng)態(tài)代理角色 主要代理真實(shí)角色的方法,
 * 被調(diào)用的方法都會(huì)走 invoke()方法,可以在該方法中處理真實(shí)角色的業(yè)務(wù)邏輯
 * 
 * @author relicemxd
 *
 */
public class StarHandler implements InvocationHandler {
   //私有的被代理角色
    private Object star;
    private final String PROXY_NAME = "代理人:";

    /*
     * Obj 傳入的是需要被代理的真實(shí)角色
     * 并且通過下面的反射技術(shù)獲取到要代理的行為
     */
    public StarHandler(Object star) {
        super();
        this.star = star;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 所有掉代理類的方法都會(huì)走這里

        // TODO 1. 在轉(zhuǎn)調(diào)具體目標(biāo)對(duì)象之前,可以執(zhí)行一些預(yù)處理邏輯
        System.out.println(PROXY_NAME + "面談");
        System.out.println(PROXY_NAME + "簽合同");
        System.out.println(PROXY_NAME + "預(yù)付款");
        System.out.println(PROXY_NAME + "安排行程");

        Object invoke = null;
        // 因?yàn)閜roxy每調(diào)用的方法都會(huì)走這里, 因此就可以通過 invoke的特性來做一些邏輯判斷
        if (method.getName().equals("sing")) {
            // TODO 2. 轉(zhuǎn)調(diào)具體目標(biāo)對(duì)象的方法
            // 只有當(dāng)代理對(duì)象調(diào)用到了sing的方法,才進(jìn)入
            invoke = method.invoke(star, args);
        }
        // TODO 3.在轉(zhuǎn)調(diào)具體目標(biāo)對(duì)象之后,可以執(zhí)行一些后處理邏輯
        System.out.println(PROXY_NAME + "收尾款");

        return invoke;
    }
}
  • 客戶端類
public class Client {
    public static void main(String[] args) {
        // 陳奕迅準(zhǔn)備要找代理(真實(shí)角色)
        EasonStar star = new EasonStar();
        // 找到了這個(gè)代理人代理(代理角色)
        StarHandler handler = new StarHandler(star);

        // jdk提供的代理實(shí)例
        StarInterface proxyStar = (StarInterface) Proxy.newProxyInstance(
                ClassLoader.getSystemClassLoader(), // 類加載器
                new Class[] { StarInterface.class }, // 這里必須是接口,否則會(huì)報(bào)錯(cuò)
                handler);// 代理類

        // 調(diào)用interview 會(huì)進(jìn)入StarHandler類的`invoke方法`,
        // 但是interview方法不會(huì)被執(zhí)行
        // proxyStar.interview();
        // 這里調(diào)用的是 EasonStar的sing方法
        proxyStar.sing();
        // proxyStar.finalPayment();
    }
}

通過動(dòng)態(tài)代理可以看出對(duì)抽象角色我們無需知道他是什么時(shí)候創(chuàng)建的,也不用知道改接口實(shí)現(xiàn)了什么,并且實(shí)現(xiàn)的是InvocationHandler接口,接口的唯一方法invoke()用于處理真實(shí)角色的邏輯.

靜態(tài)代理vs動(dòng)態(tài)代理?

可以從代理的接口和創(chuàng)建過程來分析下他們的不同之處,進(jìn)一步了解下代理模式.

代理中的接口

  • 共同之處
  • 都會(huì)在代理角色中會(huì)創(chuàng)建一個(gè)私有的成員變量
  • 都需要通過接口來實(shí)現(xiàn)代理,主要利用java多態(tài)的特性
  • 不同之處
    • 靜態(tài)代理的真實(shí)角色代理角色都會(huì)實(shí)現(xiàn)同一接口(抽象角色),動(dòng)態(tài)代理則只有真實(shí)角色實(shí)現(xiàn)了接口.
    • 靜態(tài)代理利用了java的多態(tài)特性來實(shí)現(xiàn)代理模式,而動(dòng)態(tài)代理巧妙的使用了jdk的反射機(jī)制來完成代理,也因此兩者區(qū)別在于代理角色的實(shí)現(xiàn)方式不一樣,看下面這條描述.
    • 靜態(tài)代理的代理角色實(shí)現(xiàn)的是抽象角色這個(gè)接口,而動(dòng)態(tài)代理實(shí)現(xiàn)的是jdk的內(nèi)置接口InvocationHandler.

創(chuàng)建代理的過程

  • 共同之處

    • 兩者的創(chuàng)建原理一致,需要通過創(chuàng)建代理角色來處理真實(shí)角色的一些業(yè)務(wù)邏輯.
  • 不同之處

    • 靜態(tài)代理可以直接編碼創(chuàng)建,而動(dòng)態(tài)代理是利用反射機(jī)制來抽象出代理類的創(chuàng)建過程.
    • 靜態(tài)代理我們知根知底,要對(duì)哪個(gè)接口、哪個(gè)實(shí)現(xiàn)類來創(chuàng)建代理類,所以我們?cè)诰幾g前就直接實(shí)現(xiàn)與實(shí)現(xiàn)類相同的接口,直接在實(shí)現(xiàn)的方法中調(diào)用實(shí)現(xiàn)類中的相應(yīng)(同名)方法即可;而動(dòng)態(tài)代理不同,我們不知道它什么時(shí)候創(chuàng)建,也不知道要?jiǎng)?chuàng)建針對(duì)哪個(gè)接口、實(shí)現(xiàn)類的代理類(因?yàn)樗窃谶\(yùn)行時(shí)因需實(shí)時(shí)創(chuàng)建的).
    • 在客戶端中靜態(tài)代理利用接口的多態(tài)來調(diào)用被代理方法,而動(dòng)態(tài)代理則比較復(fù)雜通過Proxy.newProxyInstance來創(chuàng)建一個(gè)代理實(shí)例從而進(jìn)行代理.這里有人會(huì)問使用的不是反射嗎?也沒跟接口有關(guān)聯(lián),其實(shí)同樣也是使用了多態(tài).使用接口指向代理類的實(shí)例,最后會(huì)用該實(shí)例來進(jìn)行具體方法的調(diào)用即可.

優(yōu)缺點(diǎn)是什么?

優(yōu)點(diǎn):

  • 拓展新好
    動(dòng)態(tài)代理,不需要更改原有的代碼,能在運(yùn)行過程中根據(jù)接口的類型動(dòng)態(tài)的調(diào)用真實(shí)角色,符合開閉原則.

  • 解耦
    代理角色可以說是一個(gè)中介,隔離了客戶端和真實(shí)角色.

缺點(diǎn):

  • 代碼量大
    靜態(tài)代理,需要接口和類,有比較多的重復(fù)代碼,降低了維護(hù)性.

  • 編譯效率?
    動(dòng)態(tài)代理,使用的是反射機(jī)制相對(duì)效率會(huì)降低,但實(shí)際差別如何,見下面測(cè)試代碼.

  • 代碼可讀性
    都是對(duì)于接口實(shí)現(xiàn)進(jìn)行代理,因此代碼的可讀性都不會(huì)很好.

兩者的效率如何?

下面我對(duì)代理sing()方法進(jìn)行了代理測(cè)試.

public class Client {
    public static void main(String[] args) throws Exception {
        // 陳奕迅準(zhǔn)備要找代理(真實(shí)角色)
        EasonStar star = new EasonStar();

        long start = System.currentTimeMillis();

        int threadNum = 10;
        CountDownLatch latch = new CountDownLatch(threadNum);

        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        dynamicProy(star);// 163

                        // staticProxy(star);//96
                    }
                    latch.countDown();
                }
            }).start();
        }

        latch.await();

        long end = System.currentTimeMillis();

        System.out.println("總共耗時(shí):" + (end - start));
    }

    public static void dynamicProy(EasonStar star) {
        // 找到了這個(gè)代理人代理(代理角色)
        StarHandler handler = new StarHandler(star);
        // jdk提供的代理實(shí)例
        StarInterface proxyStar = (StarInterface) Proxy.newProxyInstance(
                ClassLoader.getSystemClassLoader(), // 類加載器
                new Class[] { StarInterface.class }, // 這里必須是接口,否則會(huì)報(bào)錯(cuò)
                handler);// 代理類

        proxyStar.sing();
    }

    public static void staticProxy(EasonStar star) {
        // 找到代理人,專門為陳奕迅代理
        ProxyStar proxyStar = new ProxyStar(star);
        proxyStar.sing();
    }
}

輸出結(jié)果:

dynamicProy(star);// 163ms
staticProxy(star);// 96ms

靜態(tài)代理的效率稍微會(huì)比動(dòng)態(tài)代理快一些,不過也沒有差別很大,因此在選擇代理模式類別時(shí),最好還是根據(jù)項(xiàng)目需求來篩選出合適的代理模式.

代理的應(yīng)用場(chǎng)景?

那么代理在哪里會(huì)用到呢?如果你的項(xiàng)目有這幾個(gè)方面的需求可以考慮使用.

?   當(dāng)需要用一個(gè)消耗資源較少的對(duì)象來代表一個(gè)消耗資源較多的對(duì)象,從而降低系統(tǒng)開銷、縮短運(yùn)行時(shí)間時(shí)可以使用虛擬代理,例如一個(gè)對(duì)象需要很長(zhǎng)時(shí)間才能完成加載時(shí)。
?   當(dāng)需要為某一個(gè)被頻繁訪問的操作結(jié)果提供一個(gè)臨時(shí)存儲(chǔ)空間,以供多個(gè)客戶端共享訪問這些結(jié)果時(shí)可以使用緩沖代理。通過使用緩沖代理,系統(tǒng)無須在客戶端每一次訪問時(shí)都重新執(zhí)行操作,只需直接從臨時(shí)緩沖區(qū)獲取操作結(jié)果即可。
?   當(dāng)需要控制對(duì)一個(gè)對(duì)象的訪問,為不同用戶提供不同級(jí)別的訪問權(quán)限時(shí)可以使用保護(hù)代理。
?   當(dāng)需要為一個(gè)對(duì)象的訪問(引用)提供一些額外的操作時(shí)可以使用智能引用代理。

代理的分類

當(dāng)然除了我最常用的靜態(tài),動(dòng)態(tài)代理之外根據(jù)代理的實(shí)現(xiàn)與目標(biāo)不同還可以分成下面幾種代理,具體見其他代理模式

  • 安全代理:
    屏蔽對(duì)真實(shí)角色的直接訪問.
  • 遠(yuǎn)程代理:
    通過代理角色遠(yuǎn)程方法調(diào)用(RMI)
  • 延遲加載:
    先加載輕量級(jí)代理角色,真正需要再加載真實(shí)角色.
  • 虛擬代理
    允許內(nèi)存開銷較大的對(duì)象在需要的時(shí)候創(chuàng)建.只有我們真正需要這個(gè)對(duì)象的時(shí)候才創(chuàng)建.
  • 保護(hù)代理
    為不同的客戶提供不同級(jí)別的目標(biāo)對(duì)象訪問權(quán)限.

如,你要開發(fā)一個(gè)大文檔查看軟件,大文檔中有大的圖片,有可能一個(gè)圖片有100MB,在打開文潔時(shí)不能將所有的圖片都顯示出來,這樣就可以使用代理模式,當(dāng)需要查看圖片時(shí),用proxy來進(jìn)行大圖片的打開.

項(xiàng)目中有沒使用過呢?

在android中目前很熱門的一個(gè)網(wǎng)路工具retrofit源碼中也使用了動(dòng)態(tài)代理.

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) 

//這里是不是是曾相識(shí)呢
Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadMethodHandler(method).invoke(args);
          }
        });
}

參考:
java靜態(tài)代理與動(dòng)態(tài)代理

公共技術(shù)點(diǎn)之 Java 動(dòng)態(tài)代理

設(shè)計(jì)模式(結(jié)構(gòu)型)之代理模式

每天設(shè)計(jì)模式-代理模式

靜態(tài)代理VS動(dòng)態(tài)代理

最后編輯于
?著作權(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)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,727評(píng)論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評(píng)論 19 139
  • 有人說感情經(jīng)得起風(fēng)雨,卻經(jīng)不起平淡,其實(shí)想想也的確如此。感情一旦走過了初戀時(shí)的新奇,熱戀時(shí)的激情,就剩下了平淡。那...
    D035牛牛_佛山閱讀 270評(píng)論 3 7

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