java基礎-設計模式之代理模式(JDK代理)

1. 什麼是代理:由接口、實現類、以及代理類組成的一種設計模式(我自己的理解,可以看下面的快速入門案例)

2. 爲什麼要用代理:在不侵入目標方法的前提下增強目標方法的功能,這也符合Java的開閉原則

3. 怎樣使用代理:代理分爲靜態代理和動態代理,我們一個一個的看

4. 首先準備好接口和被代理類以及要用到的公共方法,code

interface SingPlay {
    void singPlay();
}

class Singer implements SingPlay {

    @Override
    public void singPlay() {
        System.out.println("演唱...");
    }
}

class Common {
    public static void giveMoney() {
        System.out.println("先給表演費...");
    }
}

4. 靜態代理,code:

// 靜態代理類
class SingPlayProxy implements SingPlay {

    private final Singer singer;

    public SingPlayProxy(Singer singer) {
        this.singer = singer;
    }

    @Override
    public void singPlay() {
        Common.giveMoney(); // 我們加入了演唱前先給錢的方法
        singer.singPlay();
    }
}


// 測試靜態代理
Singer singer = new Singer();
SingPlayProxy singPlayProxy = new SingPlayProxy(singer);
singPlayProxy.singPlay();

// 測試結果
先給表演費...
演唱...

5. 上面就是我們靜態代理的整個過程。此時我們的需求變了,我想在演唱的同時,加上講相聲的表演,於是我們需要修改我們的接口以及代理類,code

interface SingPlay {
    void singPlay();
    void play();
}

class Singer implements SingPlay {

    @Override
    public void singPlay() {
        System.out.println("演唱...");
    }

    @Override
    public void play() {
        System.out.println("講相聲...");
    }
}

class Common {
    public static void giveMoney() {
        System.out.println("先給表演費...");
    }
}

6. 由於我們講相聲的表演也是收費的,所以代理類的方法也需要加入收費的邏輯,code

class SingPlayProxy implements SingPlay {

    private final Singer singer;

    public SingPlayProxy(Singer singer) {
        this.singer = singer;
    }

    @Override
    public void singPlay() {
        Common.giveMoney();
        singer.singPlay();
    }

    @Override
    public void play() {
        Common.giveMoney();
        singer.play();
    }
}

7. 在以上的基礎上,假如我又引入了跳舞、彈琴、敲鼓、朗誦等等一系列的收費方法以後,我們會發現代理類對於 Common.giveMoney() 這個方法的複用性太差了(沒有複用)。而且接口新增方法之後,代理類也需要修改,是不是很煩?至此,我們可以引出靜態代理的第一個問題:代理類在對同一個接口的多個方法實行統一處理時,代碼複用性差。代理類和被代理類之間的代碼耦合性強。此時,我們自然而然就想到了動態代理,接下來我們看看動態代理怎麼做

8. 對同一接口的不同方法實行統一處理,動態代理,code

// 動態代理代碼
Singer singer = new Singer();
SingPlay singPlayProxy = (SingPlay) Proxy.newProxyInstance(
        DynamicProxy.class.getClassLoader(), // 第一個參數:一般是動態代理類的類加載器(這個好像寫Singer以及SingerPlay都可以..)
        Singer.class.getInterfaces(), // 第二個參數:被代理類的接口(這個不能寫錯)
        (proxy, method, args1) -> { // 第三個參數:InvocationHandler接口的lambda表達式
            Common.giveMoney(); // 這裏寫一次就可以了, 也可以根據不同的方法名特殊處理
            method.invoke(singer, args1); 
            return null;
        });
singPlayProxy.singPlay();
singPlayProxy.play();

// 測試結果
先給表演費...
演唱...
先給表演費...
講相聲...

9. 可以看到,動態代理 Common.giveMoney() 方法寫一次就可以在所有的方法上應用了,實現了代碼的複用。對於動態代理的參數什麼,以及原理什麼的,等下會發一個大佬的鏈接,這會兒可以先向下接着看。

10. 在第5步的基礎上,假如我們的需求又變了,我們需要增加一個跳舞功能的接口和唱歌並列,於是我們要增加我們的代理類以及接口,code

// 唱歌、講相聲
interface SingPlay {
    void singPlay();
    void play();
}

class Singer implements SingPlay {

    @Override
    public void singPlay() {
        System.out.println("演唱...");
    }

    @Override
    public void play() {
        System.out.println("相聲...");
    }
}

// 跳舞(新加入)
interface DancePlay {
    void dancePlay();
}

class Dancer implements DancePlay {

    @Override
    public void dancePlay() {
        System.out.println("跳舞...");
    }
}

class Common {
    public static void giveMoney() {
        System.out.println("先給表演費...");
    }
}

11. 我們還需要增加一個支持跳舞表演的代理類,code

// 唱歌、講相聲的代理類
class SingPlayProxy implements SingPlay {

    private final Singer singer;

    public SingPlayProxy(Singer singer) {
        this.singer = singer;
    }

    @Override
    public void singPlay() {
        Common.giveMoney();
        singer.singPlay();
    }

    @Override
    public void play() {
        Common.giveMoney();
        singer.play();
    }
}

// 跳舞的代理類(新增加)
class DancePlayProxy implements DancePlay {

    private final Dancer dancer;

    public DancePlayProxy(Dancer dancer) {
        this.dancer = dancer;
    }

    @Override
    public void dancePlay() {
        Common.giveMoney();
        dancer.dancePlay();
    }
}

12. 假如我現在還需要很多接口來表示不同的表演呢,那是不是我需要創建很多的代理類,隨着代理類的越來越多,項目會越來越臃腫,越來越難維護對不對。至此,我們引出了靜態代理的第二個問題:不同的接口會創建不同的代理類,造成代理類很多,代碼臃腫。我們又想到了動態代理類對不對,我們一起來看一下它會怎麼做

13. 針對不同接口,動態代理,code

// 唱歌、講相聲
Singer singer = new Singer();
SingPlay singPlayProxy = (SingPlay) Proxy.newProxyInstance(
        DynamicProxy.class.getClassLoader(),
        Singer.class.getInterfaces(),
        (proxy, method, args1) -> {
            Common.giveMoney(); // 先給錢
            method.invoke(singer, args1);
            return null;
        });
singPlayProxy.singPlay();
singPlayProxy.play();

System.out.println();
// 跳舞
Dancer dancer = new Dancer();
DancePlay dancerPlayProxy = (DancePlay) Proxy.newProxyInstance(
        DynamicProxy.class.getClassLoader(),
        Dancer.class.getInterfaces(),
        (proxy, method, args1) -> {
            Common.giveMoney(); // 先給錢
            method.invoke(dancer, args1);
            return null;
        });
dancerPlayProxy.dancePlay();


// 測試結果
先給表演費...
演唱...
先給表演費...
相聲...

先給表演費...
跳舞...

14. 上面就是動態代理的例子,和第11步相比是不是舒爽多了,而且我們還可以封裝生成動態代理代碼的部分,使我們的動態代理代碼更加簡潔。

15. 除了上面這些區別之外,我們還要在根據名字區分一下兩個代理:

  • 靜態代理:靜態,在編譯時生成代理類和.class文件
  • 動態代理:動態,在運行時生成代理類和.class文件

16. 對於動態代理的運行原理,可以參考這位大佬的博文(我就不按照大佬的思路造輪子了,仰視一下大佬吧,哈哈哈哈~):

https://mp.weixin.qq.com/s/h-hNC45BvTR499kDNg-Vuw

===============================================================================================

上面這些就是自己對java代理的理解,學無止境,可能有些不對的地方,希望大佬們指正。

拜拜~~~

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