設計模式--代理模式(Proxy Pattern)-動態(tài)代理

說明:上一節(jié)吉挣,我們一起學習了靜態(tài)代理际跪,本章我將繼續(xù)動態(tài)代理的講述幔戏,不過本章的內容是建立在第一章知識點的基礎上進行的找颓。二者之間有著邏輯上的必然聯(lián)系彪蓬。如果你還沒有看過設計模式--代理模式(Proxy Pattern)-靜態(tài)代理呛牲,我建議你先看一下,會對本節(jié)內容的理解有所幫助伟阔!

動態(tài)代理

(1) 動態(tài)代理
  代理類在程序運行時創(chuàng)建的代理方式被成為動態(tài)代理狮鸭。 我們上面靜態(tài)代理的例子中春弥,代理類(studentProxy)是自己定義好的逃呼,在程序運行之前就已經編譯完成增炭。然而動態(tài)代理隙姿,代理類并不是在Java代碼中定義的饲嗽,而是在運行時根據(jù)我們在Java代碼中的“指示”動態(tài)生成的叶圃。相比于靜態(tài)代理鹿蜀, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理,而不用修改每個代理類中的方法琐簇。 比如說,想要在每個代理的方法前都加上一個處理方法:

 public void giveMoney() {
        //調用被代理方法前加入處理方法
        beforeMethod();
        stu.giveMoney();
    }

說明:
  這里只有一個giveMoney方法座享,就寫一次beforeMethod方法婉商,但是如果除了giveMonney還有很多其他的方法,那就需要寫很多beforeMethod方法渣叛,麻煩丈秩。那看看下面動態(tài)代理如何實現(xiàn)。

(2) 動態(tài)代理簡單實現(xiàn)
  自JDK1.3開始淳衙,Java語言通過在java.lang.reflect庫中提供下面三個類直接支持代理:Proxy蘑秽、Method和InvocationHandler。
  其中Proxy使得設計師能夠在運行時創(chuàng)建代理對象箫攀,其類圖如下圖所示:

53.png

那么代理對象的創(chuàng)建過程又到底是怎樣的呢肠牲?
<1> 創(chuàng)建一個動態(tài)代理對象的過程:
1.創(chuàng)建一個InvocationHandler對象

//創(chuàng)建一個與代理對象相關聯(lián)的InvocationHandler
 InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);

2.使用Proxy類的getProxyClass靜態(tài)方法生成一個動態(tài)代理類的Class對象stuProxyClass

Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), 
                                             new Class<?>[] {Person.class});

3.獲得stuProxyClass 中一個帶InvocationHandler參數(shù)的構造器constructor

Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);

4.通過構造器constructor來創(chuàng)建一個動態(tài)實例stuProxy

Person stuProxy = (Person) cons.newInstance(stuHandler);

這樣一個動態(tài)代理對象就創(chuàng)建成功了!
然而上面的四個步驟可以通過調用Proxy的newProxyInstance()方法直接生成:

 //創(chuàng)建一個與代理對象相關聯(lián)的InvocationHandler
  InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//創(chuàng)建一個代理對象stuProxy靴跛,代理對象的每個執(zhí)行方法都會替換執(zhí)行Invocation中的invoke方法
  Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);


代理對象我們是知道如何創(chuàng)建的缀雳,但是,代理對象又是如何來代替真實對象的呢梢睛?下面是一個完整的例子:
<2> 動態(tài)代理完整實例
1.定義一個Person接口

/**
 * Created by yucheng on 2018/8/9.
 */
public interface Person {
    // 上交班費
    void giveMoney();
}

2.創(chuàng)建需要被代理的實際類Student

/**
 * Created by yucheng on 2018/8/9.
 */
public class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }
    @Override
    public void giveMoney() {
        System.out.println(name + "上交班費50元");
    }
}

3.創(chuàng)建StuInvocationHandler類肥印,實現(xiàn)InvocationHandler接口
  這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法绝葡,所有執(zhí)行代理對象的方法都會被替換成執(zhí)行invoke方法深碱。再在invoke方法中執(zhí)行被代理對象target的相應方法。當然藏畅,在代理過程中敷硅,我們在真正執(zhí)行被代理對象的方法前加入自己其他處理。這也是Spring中的AOP實現(xiàn)的主要原理墓赴。

/**
 * Created by yucheng on 2018/8/9.
 */
public class StuInvocationHandler implements InvocationHandler {
    //invocationHandler持有的被代理對象
    // 如果知道竞膳,被代理類的類型,可以在這里直接設定特定類型诫硕,需要注意的是
    // 在invoke()方法中也需要轉為相應類型坦辟,而不能使用O
    Object target;

    public StuInvocationHandler(Object target) {
        this.target = target;
    }
    /**
     * proxy:代表動態(tài)代理對象
     * method:代表正在執(zhí)行的方法
     * args:代表調用目標方法時傳入的實參
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執(zhí)行" +method.getName() + "方法");
        // 在方法調用之前進行系列操作,這里可以是方法
        System.out.println("Before the function");
        // 注意:對args進行操作之前需要進行為空的判斷,因為有的方法不帶參數(shù),不進行判斷褒脯,會拋出NullPointerException
        if (args != null){
            for (Object arg : args) {
                System.out.println(" " + arg);
            }
        }
        Object result = method.invoke(target, args);
        System.out.println("After the function");
        return result;
    }
}

4.創(chuàng)建代理對象衫生,并測試

/**
 * Created by yucheng on 2018/8/9.
 */
public class ProxyTest {
    public static void main(String[] args) {
        //創(chuàng)建一個實例對象列敲,這個對象是被代理的對象
        Person zhangsan = new Student("張三");
        //創(chuàng)建一個與代理對象相關聯(lián)的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler(zhangsan);
        //創(chuàng)建一個代理對象stuProxy來代理zhangsan彼棍,代理對象的每個執(zhí)行方法都會替換執(zhí)行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(
                Person.class.getClassLoader(),
                // Or zhangsan.getClass().getInterfaces(),
                new Class<?>[]{Person.class},
                stuHandler);
        //代理執(zhí)行上交班費的方法
        stuProxy.giveMoney();
    }
}

5.輸出結果
  我們執(zhí)行這個ProxyTest類并巍,先想一下咐扭,我們創(chuàng)建了一個需要被代理的學生張三子寓,將zhangsan對象傳給了stuHandler中梁厉,我們在創(chuàng)建代理對stuProxy時辜羊,將stuHandler作為參數(shù)了的,上面也有說到所有執(zhí)行代理對象的方法都會被替換成執(zhí)行invoke方法词顾,也就是說八秃,最后執(zhí)行的是StuInvocationHandler中的invoke方法。因此出現(xiàn)下面的結果也就正常了肉盹。

代理執(zhí)行giveMoney方法
Before the function
張三上交班費50元
After the function

動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理昔驱,而不用修改每個代理類中的方法。是因為所有被代理執(zhí)行的方法上忍,都是通過在InvocationHandler中的invoke方法調用的骤肛,所以我們只要在invoke方法中統(tǒng)一處理,就可以對所有被代理的方法進行相同的操作了窍蓝。例如腋颠,這里的方法計時,所有的被代理對象執(zhí)行的方法都會被計時它抱,然而我只做了很少的代碼量秕豫。
  動態(tài)代理的過程,代理對象和被代理對象的關系不像靜態(tài)代理那樣一目了然观蓄,清晰明了混移。因為動態(tài)代理的過程中,我們并沒有實際看到代理類侮穿,也沒有很清晰地的看到代理類的具體樣子歌径,而且動態(tài)代理中被代理對象和代理對象是通過InvocationHandler來完成的代理過程的,其中具體是怎樣操作的亲茅,為什么代理對象執(zhí)行的方法都會通過InvocationHandler中的invoke方法來執(zhí)行回铛。帶著這些問題,我們就需要對java動態(tài)代理的源碼進行簡要的分析克锣,弄清楚其中緣由茵肃。

(3) 動態(tài)代理原理分析
<1> Java動態(tài)代理創(chuàng)建出來的動態(tài)代理類Proxy
  我們創(chuàng)建動態(tài)代理對象,是通過Proxy的newProxyInstance()方法實現(xiàn)的袭祟,下面我們來看看它的源碼

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         * 2.查找或生成指定的代理類的Class對象
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            // 3.通過cl和構造器參數(shù)验残,返回一個帶InvocationHandler參數(shù)的構造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            // 1.通過參數(shù)h傳遞一個InvocationHandler對象
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 4.創(chuàng)建代理對象
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

由源碼我們可以知曉,Proxy.newProxyInstance()方法是對創(chuàng)建代理對象的四個步驟的封裝巾乳。

jdk為我們的生成了一個叫$Proxy0(這個名字后面的0是編號您没,有多個代理類會一次遞增)的代理類鸟召,這個類文件時放在內存中的,我們在創(chuàng)建代理對象時氨鹏,就是通過反射獲得這個類的構造方法欧募,然后創(chuàng)建的代理實例。通過對這個生成的代理類源碼的查看仆抵,我們很容易能看出跟继,動態(tài)代理實現(xiàn)的具體過程。
  我們可以對InvocationHandler看做一個中介類镣丑,中介類持有一個被代理對象还栓,在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用传轰,把外部對invoke的調用最終都轉為對被代理對象的調用。
  代理類調用自己方法時谷婆,通過自身持有的中介類對象來調用中介類對象的invoke方法慨蛙,從而達到代理執(zhí)行被代理對象的方法。也就是說纪挎,動態(tài)代理通過中介類實現(xiàn)了具體的代理功能

總結

生成的代理類:$Proxy0 extends Proxy implements Person期贫,我們看到代理類繼承了Proxy類,所以也就決定了java動態(tài)代理只能對接口進行代理异袄,Java的繼承機制注定了這些動態(tài)代理類們無法實現(xiàn)對class的動態(tài)代理通砍。
  上面的動態(tài)代理的例子,其實就是AOP的一個簡單實現(xiàn)了烤蜕,在目標對象的方法執(zhí)行之前和執(zhí)行之后進行了處理封孙,對方法耗時統(tǒng)計。Spring的AOP實現(xiàn)其實也是用了Proxy和InvocationHandler這兩個東西的讽营。



說明:
本文是本人綜合<<Java與模式>>以及一些好的博客的內容虎忌,再加上個人的一些心得,總結而成的橱鹏。僅僅出于學習的目的膜蠢。
參考鏈接:
1.https://www.cnblogs.com/gonjan-blog/p/6685611.html
2.http://www.runoob.com/design-pattern/proxy-pattern.html

推薦閱讀:
代理模式之“高老莊悟空降八戒”

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市莉兰,隨后出現(xiàn)的幾起案子挑围,更是在濱河造成了極大的恐慌,老刑警劉巖糖荒,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杉辙,死亡現(xiàn)場離奇詭異,居然都是意外死亡寂嘉,警方通過查閱死者的電腦和手機奏瞬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門枫绅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人硼端,你說我怎么就攤上這事并淋。” “怎么了珍昨?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵县耽,是天一觀的道長。 經常有香客問我镣典,道長兔毙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任兄春,我火速辦了婚禮澎剥,結果婚禮上,老公的妹妹穿的比我還像新娘赶舆。我一直安慰自己哑姚,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布芜茵。 她就那樣靜靜地躺著叙量,像睡著了一般。 火紅的嫁衣襯著肌膚如雪九串。 梳的紋絲不亂的頭發(fā)上绞佩,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音猪钮,去河邊找鬼品山。 笑死,一個胖子當著我的面吹牛烤低,可吹牛的內容都是我干的谆奥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拂玻,長吁一口氣:“原來是場噩夢啊……” “哼酸些!你這毒婦竟也來了?” 一聲冷哼從身側響起檐蚜,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤魄懂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闯第,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體市栗,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了填帽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛛淋。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖篡腌,靈堂內的尸體忽然破棺而出褐荷,到底是詐尸還是另有隱情,我是刑警寧澤嘹悼,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布叛甫,位于F島的核電站,受9級特大地震影響杨伙,放射性物質發(fā)生泄漏其监。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一限匣、第九天 我趴在偏房一處隱蔽的房頂上張望抖苦。 院中可真熱鬧,春花似錦米死、人聲如沸睛约。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贸伐,卻和暖如春勘天,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捉邢。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工脯丝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伏伐。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓宠进,卻偏偏與公主長得像,于是被迫代替她去往敵國和親藐翎。 傳聞我的和親對象是個殘疾皇子材蹬,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容