代理和動態代理

轉自Java3y公眾號,僅個人學習使用

一、代理模式介紹

代理模式簡介

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

這里有一個程序員接口,他們每天就是寫代碼

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;
    }

    /yun 程序員大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方法可以生成某個對象的代理對象,該方法需要三個參數:
我們來看看究竟是怎么請水軍的:

Proxy類

參數一:生成代理對象使用哪個類裝載器【一般我們使用的是被代理類的裝載器】

?參數二:生成哪個對象的代理對象,通過接口指定【指定要被代理類的接口】

?參數三:生成的代理對象的方法里干什么事【實現handler接口,我們想怎么實現就怎么實現】

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

?代理對象擁有目標對象相同的方法【因為參數二指定了對象的接口,代理對象會實現接口的所有方法】

?用戶調用代理對象的什么方法,都是在調用處理器的invoke方法?!颈粩r截】

?使用JDK動態代理必須要有接口【參數二需要接口】

上面也說了:代理對象會實現接口的所有方法,這些實現的方法交由我們的handler來處理!

?所有通過動態代理實現的方法全部通過invoke()調用

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底層其實就是動態代理來實現的-->面向切面編程。具體可參考我之前寫的那篇文章:
其實只要記住一點:原有的對象需要額外的功能,想想動態代理這項技術!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容