給女朋友講解什麼是代理模式

Java3y發表於2018-12-28

前言

只有光頭才能變強

回顧前面:

多執行緒就先告一段落了,昨天寫完多執行緒,本來打算是看IO的知識點的,後來看了一下IO的幾種模型,又翻了一下《Java程式設計思想》。不知道從哪下手~~

在看到FilterInputStreamFilterOutputStream時看到了之前常聽見的裝飾模式(對IO一定了解的同學可能都會知道那麼一句話:在IO用得最多的就是裝飾模式了)!

看到這裡你以為我要講裝飾模式了麼?不是,今天我們來講講什麼是代理模式(就是這麼皮,裝飾模式明天講吧~)。

受知乎@Beautiful Java文章和《設計模式之禪》的啟發,我也來搞一篇腦洞小開的文章..

由標題可知,這篇文章是寫給我女朋友看的。自從她知道我開了公眾號以後就每天看我寫的文章,之前寫點小演算法的時候她覺得程式設計還是挺有意思,想去學學。可是,從我開始寫Java容器、多執行緒,她說一點都看不懂了。於是乎,現在來寫點既高大尚、又容易懂的東西


    GirlFriend girlFriend = new GirlFriend();
    sayingProxy(girlFriend);

複製程式碼

那麼接下來就開始吧,如果文章有錯誤的地方請大家多多包涵,不吝在評論區指正哦~

宣告:本文使用JDK1.8

一、代理模式介紹

代理模式是一種非常好理解的一種設計模式,生活中處處都有代理

  • 王寶強作為一個明星,不可能什麼事都由他自己幹(約電視劇、排期之類的),於是他請了經紀人
  • 去醫院掛號很麻煩怎麼辦?找黃牛幫我們掛號
  • 王者榮耀技術水平不夠,想要上分怎麼辦?請遊戲代練
  • 寫點不正經的程式碼被警察捉走了怎麼辦?請律師幫我們打官司

無論是經紀人、黃牛、遊戲代練、律師他們都是得幫我們幹活。但是他們不能一手包辦的,僅僅在“我”的基礎上處理一些雜碎的東西(我們不願意幹、或者幹不了的東西)。

  • 導演找了黃寶強的經紀人讓王寶強去拍電影
  • 黃牛去排隊讓我們能掛上號
  • 遊戲代練上分是我的微信賬號
  • 律師幫我處理法律上的問題,如果打官司失敗,牢還是由我來坐

再舉個例子:

  • 現在我是一個網紅,擁有很多粉絲。粉絲希望我天天寫程式碼給他們看,那我肯定不能天天寫程式碼啊,我豈不是很忙....於是乎,我就去找了個經紀人。這個經紀人就代表了我。當忠實粉絲想要我寫程式碼的時候,應該是先找經紀人,告訴經紀人想讓我寫程式碼。
  • 十年過去了,我越來越紅了,頭髮也越來越少。不是粉絲想要我寫程式碼,我就寫了。我要收費了。但是呢,作為一個公眾人物,不可能是我自己說:我要收10000萬,我才會去寫程式碼。於是這就讓經紀人對粉絲說:只有10000萬,我才會寫程式碼。
  • 無論外界是想要我幹什麼,都要經過我的經紀人。我的經紀人也會在其中考慮收費、推脫它們的請求。
  • 經紀人就是代理,實際寫程式碼的還是我

所以說代理模式就是:當前物件不願意乾的,沒法乾的東西委託給別的物件來做,我只要做好本分的東西就好了!

二、用程式碼描述代理模式(靜態代理)

這裡有一個程式設計師介面,他們每天就是寫程式碼

public interface Programmer {

    // 程式設計師每天都寫程式碼
    void coding();

}
複製程式碼

Java3y也是一個程式設計師,他也寫程式碼(每個程式設計師寫的程式碼都不一樣,所以分了介面和實現類)


public class Java3y implements Programmer {

    @Override
    public void coding() {
        System.out.println("Java3y最新文章:......給女朋友講解什麼是代理模式.......");
    }
}

複製程式碼

此時Java3y已經是一個網紅了,他不想枯燥地寫程式碼。他在想:“在寫程式碼時能賺錢就好咯,有人給我錢,我才寫程式碼”。但是,Java3y的文筆太爛了,一旦有什麼冬瓜豆腐,分分鐘變成過氣網紅,這是Java3y不願意看到的。

而知乎、部落格園這種平臺又不能自己給自己點贊來吸引流量(-->當前物件無法做)

所以Java3y去請了一個**程式設計師大V(代理)**來實現自己的計劃,這個程式設計師大V會每次讓Java3y發文章時,就給Java3y點贊、評論、鼓吹這文章好。只要流量有了,錢就到手了。


public class ProgrammerBigV implements Programmer {
    
    // 指定程式設計師大V要讓誰發文章(先發文章、後點贊)
    private Java3y java3y ;

    public ProgrammerBigV(Java3y java3y) {
        this.java3y = java3y;
    }

    // 程式設計師大V點贊評論收藏轉發
    public void upvote() {
        System.out.println("程式設計師大V點贊評論收藏轉發!");
    }

    @Override
    public void coding() {

        // 讓Java3y發文章
        java3y.coding();

        // 程式設計師大V點贊評論收藏轉發!
        upvote();
    }
}

複製程式碼

文章(程式碼)還是由Java3y來發,但每次傳送之後程式設計師大V都會點贊。


public class Main {

    public static void main(String[] args) {

        // 想要發達的Java3y
        Java3y java3y = new Java3y();

        // 受委託程式設計師大V
        Programmer programmer = new ProgrammerBigV(java3y);

        // 受委託程式設計師大V讓Java3y發文章,大V(自己)來點贊
        programmer.coding();
    }  
}
複製程式碼

給女朋友講解什麼是代理模式

這樣一來,不明真相的路人就覺得Java3y是真厲害,知識付費。

2.1透明代理(普通代理)

經過一段時間,Java3y嚐到甜頭了,覺得這是一條財路。於是Java3y給足了程式設計師大V錢,讓程式設計師大V只做他的生意,不能做其他人的生意(斷了其他人的財路)。

於是乎,程式設計師大V做Java3y一個人的生意:


public class ProgrammerBigV implements Programmer {

    // 指定程式設計師大V要給Java3y點贊
    private Java3y java3y ;

    // 只做Java3y的生意了
    public ProgrammerBigV() {
        this.java3y = new Java3y();
    }

    // 程式設計師大V點贊評論收藏轉發
    public void upvote() {
        System.out.println("程式設計師大V點贊評論收藏轉發!");
    }

    @Override
    public void coding() {

        // 讓Java3y發文章了
        java3y.coding();

        // 程式設計師大V點贊評論收藏轉發!
        upvote();

    }
}
複製程式碼

於是乎,程式設計師大V想要賺點零花錢的時候直接讓Java3y發文章就好了。


public class Main {

    public static void main(String[] args) {

        // 受委託程式設計師大V
        Programmer programmer = new ProgrammerBigV();

        // 受委託程式設計師大V讓Java3y發文章,大V來點贊
        programmer.coding();
        
    }
}
複製程式碼

此時,真實物件(Java3y)對外界來說是透明的

2.2代理類自定義方法

程式設計師大V看到Java3y一直順風順水,賺大錢了。覺得是時候要加價了,於是在點贊完畢後就跟Java3y說每點完一次贊加100塊

於是乎,程式設計師大V就增添了另外一個方法:addMoney()


public class ProgrammerBigV implements Programmer {


	// ..省略了上面的程式碼

    // 加價啦
    public void addMoney() {
        System.out.println("這次我要加100塊");
    }

    @Override
    public void coding() {

        // 讓Java3y發文章了
        java3y.coding();

        // 程式設計師大V點贊評論收藏轉發!
        upvote();

        // 加價
        addMoney();

    }
}
複製程式碼

於是乎程式設計師大V每次都能多100塊:

給女朋友講解什麼是代理模式

三、動態代理

幾年時間過去了,Java3y靠著程式設計師的大V點贊還是沒發財(本質上Java3y還沒有乾貨,沒受到大眾的認可)。此時已經有很多人晉升成了程式設計師大V了,但是之前的那個程式設計師大V還是一直累加著錢...雖然在開始的時候Java3y嚐到了甜頭,但現在Java3y財政已經匱乏了。

Java3y將自己的失敗認為:一定是那個程式設計師大V轉門為我點贊被識破了,吃瓜群眾都知道了,他收費又那麼貴。

於是Java3y不請程式設計師大V了,請水軍來點贊(水軍便宜,只要能點贊就行了):

public class Main {

    public static void main(String[] args1) {

        // Java3y請水軍
        Java3y java3y = new Java3y();

        Programmer programmerWaterArmy = (Programmer) Proxy.newProxyInstance(java3y.getClass().getClassLoader(), java3y.getClass().getInterfaces(), (proxy, method, args) -> {

            // 如果是呼叫coding方法,那麼水軍就要點讚了
            if (method.getName().equals("coding")) {
                method.invoke(java3y, args);
                System.out.println("我是水軍,我來點讚了!");

            } else {
                // 如果不是呼叫coding方法,那麼呼叫原物件的方法
                return method.invoke(java3y, args);
            }

            return null;
        });

        // 每當Java3y寫完文章,水軍都會點贊
        programmerWaterArmy.coding();

    }

}

複製程式碼

每當Java3y發文章的時候,水軍都會點贊。

給女朋友講解什麼是代理模式

Java3y感嘆:請水軍真是方便啊~

3.1動態代理呼叫過程

我們來看看究竟是怎麼請水軍的:

Java提供了一個Proxy類,呼叫它的newInstance方法可以生成某個物件的代理物件,該方法需要三個引數:

這裡寫圖片描述

  • 引數一:生成代理物件使用哪個類裝載器【一般我們使用的是被代理類的裝載器】
  • 引數二:生成哪個物件的代理物件,通過介面指定【指定要被代理類的介面】
  • 引數三:生成的代理物件的方法裡幹什麼事【實現handler介面,我們想怎麼實現就怎麼實現】

在編寫動態代理之前,要明確幾個概念:

  • 代理物件擁有目標物件相同的方法【因為引數二指定了物件的介面,代理物件會實現介面的所有方法】
  • 使用者呼叫代理物件的什麼方法,都是在呼叫處理器的invoke方法。【被攔截】
  • 使用JDK動態代理必須要有介面【引數二需要介面】

上面也說了:代理物件會實現介面的所有方法,這些實現的方法交由我們的handler來處理!

  • 所有通過動態代理實現的方法全部通過invoke()呼叫

給女朋友講解什麼是代理模式

所以動態代理呼叫過程是這樣子的:

給女朋友講解什麼是代理模式

3.2靜態代理和動態代理的區別

很明顯的是:

  • 靜態代理需要自己寫代理類-->代理類需要實現與目標物件相同的介面
  • 而動態代理不需要自己編寫代理類--->(是動態生成的)

使用靜態代理時:

  • 如果目標物件的介面有很多方法的話,那我們還是得一一實現,這樣就會比較麻煩

使用動態代理時:

  • 代理物件的生成,是利用JDKAPI,動態地在記憶體中構建代理物件(需要我們指定建立 代理物件/目標物件 實現的介面的型別),並且會預設實現介面的全部方法

四、典型應用

我們之前寫中文過濾器的時候,需要使用包裝設計模式來設計一個request類。如果不是Servlet提供了實現類給我們,我們使用包裝設計模式會比較麻煩

現在我們學習了動態代理了,動態代理就是攔截直接訪問物件,可以給物件進行增強的一項技能

4.1中文過濾器


    public void doFilter(final ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        final HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        response.setContentType("text/html;charset=UTF-8");
        request.setCharacterEncoding("UTF-8");


        //放出去的是代理物件
        chain.doFilter((ServletRequest) Proxy.newProxyInstance(CharacterEncodingFilter.class.getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //判斷是不是getParameter方法
                if (!method.getName().equals("getParameter")) {

                    //不是就使用request呼叫
                   return method.invoke(request, args);
                }

                //判斷是否是get型別的
                if (!request.getMethod().equalsIgnoreCase("get")) {
                   return method.invoke(request, args);
                }

                //執行到這裡,只能是get型別的getParameter方法了。
                String value = (String) method.invoke(request, args);
                if (value == null) {
                    return null;
                }
                return new String(value.getBytes("ISO8859-1"), "UTF-8");
            }

        }), response);

    }
複製程式碼

五、總結

第一次以這種方式寫文章,舉的例子可能會不妥,希望大家見諒~

本文主要講解了代理模式的幾個要點,其實還有一些細節的:比如“強制代理”(只能通過被代理物件找到代理物件,不能繞過代理物件直接訪問被代理物件)。只是用得比較少,我就不說了~~

要實現動態代理必須要有介面的,動態代理是基於介面來代理的(實現介面的所有方法),如果沒有介面的話我們可以考慮cglib代理。

cglib代理也叫子類代理,從記憶體中構建出一個子類來擴充套件目標物件的功能

這裡我就不再貼出程式碼來了,因為cglib的代理教程也很多,與動態代理實現差不多~~~

總的來說:代理模式是我們寫程式碼中用得很多的一種模式了,Spring的AOP底層其實就是動態代理來實現的-->面向切面程式設計。具體可參考我之前寫的那篇文章:

其實只要記住一點:原有的物件需要額外的功能,想想動態代理這項技術

參考資料:

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y

文章的目錄導航

相關文章