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

轉(zhuǎn)自Java3y公眾號(hào),僅個(gè)人學(xué)習(xí)使用

一、代理模式介紹

代理模式簡(jiǎn)介

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

這里有一個(gè)程序員接口,他們每天就是寫(xiě)代碼

public interface Programmer {

    // 程序員每天都寫(xiě)代碼
    void coding();

}

Java3y也是一個(gè)程序員,他也寫(xiě)代碼(每個(gè)程序員寫(xiě)的代碼都不一樣,所以分了接口和實(shí)現(xiàn)類(lèi))

public class Java3y implements Programmer {

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

此時(shí)Java3y已經(jīng)是一個(gè)網(wǎng)紅了,他不想枯燥地寫(xiě)代碼。他在想:“在寫(xiě)代碼時(shí)能賺錢(qián)就好咯,有人給我錢(qián),我才寫(xiě)代碼”。但是,Java3y的文筆太爛了,一旦有什么冬瓜豆腐,分分鐘變成過(guò)氣網(wǎng)紅,這是Java3y不愿意看到的。

而知乎、博客園這種平臺(tái)又不能自己給自己點(diǎn)贊來(lái)吸引流量(-->當(dāng)前對(duì)象無(wú)法做)

所以Java3y去請(qǐng)了一個(gè)程序員大V(代理)來(lái)實(shí)現(xiàn)自己的計(jì)劃,這個(gè)程序員大V會(huì)每次讓Java3y發(fā)文章時(shí),就給Java3y點(diǎn)贊、評(píng)論、鼓吹這文章好。只要流量有了,錢(qián)就到手了。

public class ProgrammerBigV implements Programmer {

    // 指定程序員大V要讓誰(shuí)發(fā)文章(先發(fā)文章、后點(diǎn)贊)
    private Java3y java3y ;

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

    /yun 程序員大V點(diǎn)贊評(píng)論收藏轉(zhuǎn)發(fā)
    public void upvote() {
        System.out.println("程序員大V點(diǎn)贊評(píng)論收藏轉(zhuǎn)發(fā)!");
    }

    @Override
    public void coding() {

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

        // 程序員大V點(diǎn)贊評(píng)論收藏轉(zhuǎn)發(fā)!
        upvote();
    }
}

文章(代碼)還是由Java3y來(lái)發(fā),但每次發(fā)送之后程序員大V都會(huì)點(diǎn)贊。

public class Main {

    public static void main(String[] args) {

        // 想要發(fā)達(dá)的Java3y
        Java3y java3y = new Java3y();

        // 受委托程序員大V
        Programmer programmer = new ProgrammerBigV(java3y);

        // 受委托程序員大V讓Java3y發(fā)文章,大V(自己)來(lái)點(diǎn)贊
        programmer.coding();
    }  
}
這樣一來(lái),不明真相的路人就覺(jué)得Java3y是真厲害,知識(shí)付費(fèi)。

2.1透明代理(普通代理)

經(jīng)過(guò)一段時(shí)間,Java3y嘗到甜頭了,覺(jué)得這是一條財(cái)路。于是Java3y給足了程序員大V錢(qián),讓程序員大V只做他的生意,不能做其他人的生意(斷了其他人的財(cái)路)。

于是乎,程序員大V做Java3y一個(gè)人的生意:

public class ProgrammerBigV implements Programmer {

    // 指定程序員大V要給Java3y點(diǎn)贊
    private Java3y java3y ;

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

    // 程序員大V點(diǎn)贊評(píng)論收藏轉(zhuǎn)發(fā)
    public void upvote() {
        System.out.println("程序員大V點(diǎn)贊評(píng)論收藏轉(zhuǎn)發(fā)!");
    }

    @Override
    public void coding() {

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

        // 程序員大V點(diǎn)贊評(píng)論收藏轉(zhuǎn)發(fā)!
        upvote();

    }
}

于是乎,程序員大V想要賺點(diǎn)零花錢(qián)的時(shí)候直接讓Java3y發(fā)文章就好了。

public class Main {

    public static void main(String[] args) {

        // 受委托程序員大V
        Programmer programmer = new ProgrammerBigV();

        // 受委托程序員大V讓Java3y發(fā)文章,大V來(lái)點(diǎn)贊
        programmer.coding();

    }
}

此時(shí),真實(shí)對(duì)象(Java3y)對(duì)外界來(lái)說(shuō)是透明的。

2.2代理類(lèi)自定義方法

程序員大V看到Java3y一直順風(fēng)順?biāo)?,賺大錢(qián)了。覺(jué)得是時(shí)候要加價(jià)了,于是在點(diǎn)贊完畢后就跟Java3y說(shuō)每點(diǎn)完一次贊加100塊!

于是乎,程序員大V就增添了另外一個(gè)方法:addMoney()

public class ProgrammerBigV implements Programmer {


    // ..省略了上面的代碼

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

    @Override
    public void coding() {

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

        // 程序員大V點(diǎn)贊評(píng)論收藏轉(zhuǎn)發(fā)!
        upvote();

        // 加價(jià)
        addMoney();

    }
}

于是乎程序員大V每次都能多100塊:


運(yùn)行結(jié)果圖

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

幾年時(shí)間過(guò)去了,Java3y靠著程序員的大V點(diǎn)贊還是沒(méi)發(fā)財(cái)(本質(zhì)上Java3y還沒(méi)有干貨,沒(méi)受到大眾的認(rèn)可)。此時(shí)已經(jīng)有很多人晉升成了程序員大V了,但是之前的那個(gè)程序員大V還是一直累加著錢(qián)…雖然在開(kāi)始的時(shí)候Java3y嘗到了甜頭,但現(xiàn)在Java3y財(cái)政已經(jīng)匱乏了。

Java3y將自己的失敗認(rèn)為:一定是那個(gè)程序員大V轉(zhuǎn)門(mén)為我點(diǎn)贊被識(shí)破了,吃瓜群眾都知道了,他收費(fèi)又那么貴。

于是Java3y不請(qǐng)程序員大V了,請(qǐng)水軍來(lái)點(diǎn)贊(水軍便宜,只要能點(diǎn)贊就行了):

public class Main {

    public static void main(String[] args1) {

        // Java3y請(qǐng)水軍
        Java3y java3y = new Java3y();

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

            // 如果是調(diào)用coding方法,那么水軍就要點(diǎn)贊了
            if (method.getName().equals("coding")) {
                method.invoke(java3y, args);
                System.out.println("我是水軍,我來(lái)點(diǎn)贊了!");

            } else {
                // 如果不是調(diào)用coding方法,那么調(diào)用原對(duì)象的方法
                return method.invoke(java3y, args);
            }

            return null;
        });

        // 每當(dāng)Java3y寫(xiě)完文章,水軍都會(huì)點(diǎn)贊
        programmerWaterArmy.coding();

    }

}

每當(dāng)Java3y發(fā)文章的時(shí)候,水軍都會(huì)點(diǎn)贊。


運(yùn)行效果圖

Java3y感嘆:請(qǐng)水軍真是方便啊~

3.1動(dòng)態(tài)代理調(diào)用過(guò)程

我們來(lái)看看究竟是怎么請(qǐng)水軍的:

Java提供了一個(gè)Proxy類(lèi),調(diào)用它的newInstance方法可以生成某個(gè)對(duì)象的代理對(duì)象,該方法需要三個(gè)參數(shù):
我們來(lái)看看究竟是怎么請(qǐng)水軍的:

Proxy類(lèi)

參數(shù)一:生成代理對(duì)象使用哪個(gè)類(lèi)裝載器【一般我們使用的是被代理類(lèi)的裝載器】

?參數(shù)二:生成哪個(gè)對(duì)象的代理對(duì)象,通過(guò)接口指定【指定要被代理類(lèi)的接口】

?參數(shù)三:生成的代理對(duì)象的方法里干什么事【實(shí)現(xiàn)handler接口,我們想怎么實(shí)現(xiàn)就怎么實(shí)現(xiàn)】

在編寫(xiě)動(dòng)態(tài)代理之前,要明確幾個(gè)概念:

?代理對(duì)象擁有目標(biāo)對(duì)象相同的方法【因?yàn)閰?shù)二指定了對(duì)象的接口,代理對(duì)象會(huì)實(shí)現(xiàn)接口的所有方法】

?用戶(hù)調(diào)用代理對(duì)象的什么方法,都是在調(diào)用處理器的invoke方法。【被攔截】

?使用JDK動(dòng)態(tài)代理必須要有接口【參數(shù)二需要接口】

上面也說(shuō)了:代理對(duì)象會(huì)實(shí)現(xiàn)接口的所有方法,這些實(shí)現(xiàn)的方法交由我們的handler來(lái)處理!

?所有通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)的方法全部通過(guò)invoke()調(diào)用

invoke()方法調(diào)用

所以動(dòng)態(tài)代理調(diào)用過(guò)程是這樣子的:
動(dòng)態(tài)代理調(diào)用過(guò)程示意圖

3.2靜態(tài)代理和動(dòng)態(tài)代理的區(qū)別


很明顯的是:

?靜態(tài)代理需要自己寫(xiě)代理類(lèi)-->代理類(lèi)需要實(shí)現(xiàn)與目標(biāo)對(duì)象相同的接口

?而動(dòng)態(tài)代理不需要自己編寫(xiě)代理類(lèi)--->(是動(dòng)態(tài)生成的)

使用靜態(tài)代理時(shí):

?如果目標(biāo)對(duì)象的接口有很多方法的話,那我們還是得一一實(shí)現(xiàn),這樣就會(huì)比較麻煩

使用動(dòng)態(tài)代理時(shí):

?代理對(duì)象的生成,是利用JDKAPI,動(dòng)態(tài)地在內(nèi)存中構(gòu)建代理對(duì)象(需要我們指定創(chuàng)建 代理對(duì)象/目標(biāo)對(duì)象 實(shí)現(xiàn)的接口的類(lèi)型),并且會(huì)默認(rèn)實(shí)現(xiàn)接口的全部方法。

四、典型應(yīng)用

我們之前寫(xiě)中文過(guò)濾器的時(shí)候,需要使用包裝設(shè)計(jì)模式來(lái)設(shè)計(jì)一個(gè)request類(lèi)。如果不是Servlet提供了實(shí)現(xiàn)類(lèi)給我們,我們使用包裝設(shè)計(jì)模式會(huì)比較麻煩

現(xiàn)在我們學(xué)習(xí)了動(dòng)態(tài)代理了,動(dòng)態(tài)代理就是攔截直接訪問(wèn)對(duì)象,可以給對(duì)象進(jìn)行增強(qiáng)的一項(xiàng)技能。

4.1中文過(guò)濾器

   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");


        //放出去的是代理對(duì)象
        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調(diào)用
                   return method.invoke(request, args);
                }

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

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

        }), response);

    }

五、總結(jié)

本文主要講解了代理模式的幾個(gè)要點(diǎn),其實(shí)還有一些細(xì)節(jié)的:比如“強(qiáng)制代理”(只能通過(guò)被代理對(duì)象找到代理對(duì)象,不能繞過(guò)代理對(duì)象直接訪問(wèn)被代理對(duì)象)。只是用得比較少,我就不說(shuō)了~~

要實(shí)現(xiàn)動(dòng)態(tài)代理必須要有接口的,動(dòng)態(tài)代理是基于接口來(lái)代理的(實(shí)現(xiàn)接口的所有方法),如果沒(méi)有接口的話我們可以考慮cglib代理。

cglib代理也叫子類(lèi)代理,從內(nèi)存中構(gòu)建出一個(gè)子類(lèi)來(lái)擴(kuò)展目標(biāo)對(duì)象的功能!

這里我就不再貼出代碼來(lái)了,因?yàn)閏glib的代理教程也很多,與動(dòng)態(tài)代理實(shí)現(xiàn)差不多~~~

總的來(lái)說(shuō):代理模式是我們寫(xiě)代碼中用得很多的一種模式了,Spring的AOP底層其實(shí)就是動(dòng)態(tài)代理來(lái)實(shí)現(xiàn)的-->面向切面編程。具體可參考我之前寫(xiě)的那篇文章:
其實(shí)只要記住一點(diǎn):原有的對(duì)象需要額外的功能,想想動(dòng)態(tài)代理這項(xiàng)技術(shù)!

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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