Java學習:Java動態代理(jdk)

背景:學習spring的AOP或者EasyMock的源碼時,需要對java的動態代理有深刻的了解

關于cglib的動態代理實現可以參考:
Java動態代理(cglib)

java中可以通過jdk中的java.lang.reflect.Proxy類來實現動態代理,但是我們需要先了解下什么是代理模式和靜態代理

1.代理模式簡介、目的,以及靜態代理簡介
代理模式構成要素通常是1個接口Subject,2個類RealSubject、Proxy,通用的類圖如下:


范例代碼如下:

  • Subject接口
public interface Subject {
    void play();

    void upgrade(Integer level);

    void rest();
}
  • RealSubject類
public class RealSubject implements Subject {
    @Override
    public void play() {
        System.out.println("the client is playing");
    }

    @Override
    public void upgrade(Integer level) {
        System.out.println("the client is upgrading");
    }

    @Override
    public void rest() {
        System.out.println("the client is resting");
    }
}
  • Proxy類
public class ProxySubject implements Subject {

    private Subject proxied = null;

    /**
     * bind the proxied object
     *
     * @param client
     */
    public ProxySubject(Subject client) {
        this.proxied = client;
    }

    @Override
    public void play() {
        // do something before play
        System.out.println("before the client is playing");
        this.proxied.play();
        System.out.println("after the client is playing");
        // do something after play
    }

    @Override
    public void upgrade(Integer level) {
        // do something before upgrade
        System.out.println("before the client is upgrading");
        this.proxied.upgrade(level);
        System.out.println("before the client is upgrading");
        // do something after upgrade
    }

    @Override
    public void rest() {
        // do something before rest
        System.out.println("before the client is resting");
        this.proxied.rest();
        System.out.println("before the client is resting");
        // do something after rest
    }
}
  • 調用代碼:
public class TestMain {
    public static void main(String[] args) {
        ProxySubject proxy = new ProxySubject(new RealSubject());
        proxy.play();
        proxy.rest();
        proxy.upgrade(1);
    }
}
  • 運行結果:

代理模式就是當我們對RealSubject的行為進行增強,但是我們又不希望直接修改RealSubject的代碼而給出的一種無侵入式方案

1.代理模式講清楚了,那么什么是靜態代理?
當我們對一個RealSubject類進行強化時,寫一個代理類Proxy就可以了,這就是靜態代理。
2.為什么要有動態代理?
但是如果我們需要對成千上萬個RealSubject類進行強化時,是不可能去寫這么多代理類的,會造成項目代碼量暴增,代碼難于管理,這時候就需要動態代理了

動態代理的含義:在jvm中動態創建一個Proxy類,使用完之后,立即銷毀Proxy類,你在項目代碼中是看不到這個Proxy類的源碼的

2.如何實現動態代理?
動態代理的原理就是:在jvm的運行時動態創建一個類,并實例化

  • 在java中,創建一個類的流程如下:

1.source經過編譯變成.class文件
2.java應用在啟動時,通過類加載器(ClassLoader)加載.class文件到jvm中并轉換為對應的class類實例
3.通過class類實例new一個對象

這里"class類實例"指的是public final class Class<T>的實例,詳情請見jdk源碼

動態代理希望能省略source編譯成.class文件的過程,在runtime中直接生成.class文件并通過自定義ClassLoader加載.class文件到jvm,后續的流程和正常類加載流程沒有區別

  • 有哪些方法可以在運行時的代碼中生成二進制字節碼?
    Java字節碼生成開源框架:JavaAssist和ASM(可以單開一文來講這2個框架是如何使用的)

JavaAssist和ASM相當于通過編寫java代碼生成class文件,當需要生成的class文件業務邏輯較復雜時,需要編寫的java代碼量較大,難于規模應用,那么我們java當前是通過什么方式實現動態代理的?

3.jdk的動態代理
jdk的動態代理實現中,相對于代理模式通用的Subject,RealSubject,Proxy,還多了一個InvocationHandler接口,它的用途是:
當Proxy需要調用RealSubject的方法時,不再直接調用,而是將方法對應的Method變量和RealSubject實例傳入InvocationHandler,由InvocationHandler來負責調用,并由InvocationHandler對方法調用進行增強

InvocationHandler的加入有什么好處?
我們思考這樣一種場景,RealSubject有20個方法,現在需要對這20個方法做同樣的強化邏輯,原本的經典代理模式是怎么做的?需要重復寫20次強化邏輯代碼

有了InvocationHandler之后,我們在Proxy的方法重寫處調用InvocationHandler的invoke方法,強化邏輯都是在invoke方法中實現

4.jdk動態代理范例代碼

  • Subject和RealSubject代碼不變,只是Proxy類不用再手動去寫,而是通過Proxy.newProxyInstance()的方法去實現
  • InvocationHanlder實現:
public class MyInvocationHandler implements InvocationHandler {

    private RealSubject subject = null;

    public MyInvocationHandler(RealSubject subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("before the method is invoked");

        if (method.getName().equals("upgrade")) {
            method.invoke(this.subject, args);
        } else {
            method.invoke(this.subject, null);
        }

        System.out.println("after the method is invoked");

        return null;
    }
}
  • 測試代碼:
public class TestMain {
    public static void main(String[] args) {
        RealSubject subject = new RealSubject();

        // 生成InvocationHandler實例
        MyInvocationHandler h = new MyInvocationHandler(subject);

        // 獲取被代理對象實現的接口列表
        Class[] interfaces = subject.getClass().getInterfaces();

        // 獲取被代理對象的classloader
        ClassLoader classLoader = subject.getClass().getClassLoader();

        Subject proxy = (Subject) Proxy.newProxyInstance(classLoader, interfaces, h);

        proxy.play();

        proxy.rest();

        proxy.upgrade(1);
    }
}

其中Proxy.newProxyInstance(classLoader,interfaces,h)實現了創建Proxy類的功能

可以看到,jdk的動態代理,是對接口中定義方法的強化,如果要直接實現對某個類的強化,而這個類又沒有實現任何接口,就需要借助cglib了
這跟newProxyInstance的實現有關系,newProxyInstance的參數是:classLoader,interfaces,h,可以理解是這個方法是基于interfaces去創建的動態代理,如果我們能夠在Proxy類中新建一個創建動態代理的方法,用realSubject作為參數,基于realSubject去創建代理類,那么我們也就在jdk中實現了對具體某個類的動態代理實現

下面的代碼是對上面測試代碼的修改,動態代理的創建完全跟RealSubject這個類沒有關系了,也可以看出來jdk的基于接口的動態代理的含義了:

public class TestMain {
    public static void main(String[] args) {
        Subject subject = new Subject() {
            @Override
            public void play() {

            }

            @Override
            public void upgrade(Integer level) {

            }

            @Override
            public void rest() {

            }
        };

        // 生成InvocationHandler實例
        MyInvocationHandler h = new MyInvocationHandler(subject);

        // 獲取被代理對象實現的接口列表
        Class[] interfaces = new Class[]{Subject.class};

        // 獲取被代理對象的classloader
        ClassLoader classLoader = subject.getClass().getClassLoader();

        Subject proxy = (Subject) Proxy.newProxyInstance(classLoader, interfaces, h);

        proxy.play();

        proxy.rest();

        proxy.upgrade(1);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容