淺談Java動態(tài)代理

代理模式

相信了解設計模式的developer對代理(proxy pattern)模式都不陌生伐憾。代理模式的基本思想就是在調(diào)用者和被調(diào)用者之間加上一層“代理”壮韭,這層代理對于調(diào)用者而言是透明的,因為代理往往和被代理對象實現(xiàn)相同的借口枉圃。那么既然實現(xiàn)相同的接口遮糖,代理的意義又何在落追?因為我們常常需要在原本的接口上封裝一些業(yè)務邏輯,比如日志偿洁、緩存撒汉、訪問控制等等,這些另外封裝的業(yè)務邏輯從設計的角度并不適宜直接封裝在原有的接口實現(xiàn)中父能,因為諸如日志神凑、緩存等并不屬于被代理對象的職責;同時何吝,代理模式可以做到方便的修改和移除(設計模式的關鍵就是封裝變化)溉委。這種模式在RMI和EJB中都得到了很好的體現(xiàn),包括spring的AOP中實現(xiàn)爱榕。傳統(tǒng)的代理模式需要在源代碼中添加一些附加的類瓣喊,一般需要手工編寫或工具自動生成。

動態(tài)代理

java的動態(tài)代理比代理的思想更進了一步黔酥,因為它可以動態(tài)的創(chuàng)建代理并動態(tài)的處理對所代理方法的調(diào)用藻三,在運行時刻,可以動態(tài)創(chuàng)建出一個實現(xiàn)了多個接口的代理類跪者。每個代理類的對象都會關聯(lián)一個表示內(nèi)部處理邏輯的InvocationHandler 接 口的實現(xiàn)棵帽。當使用者調(diào)用了代理對象所代理的接口中的方法的時候,這個調(diào)用的信息會被傳遞給InvocationHandler的invoke方法渣玲。在 invoke方法的參數(shù)中可以獲取到代理對象逗概、方法對應的Method對象和調(diào)用的實際參數(shù)。invoke方法的返回值被返回給使用者忘衍。這種做法實際上相 當于對方法調(diào)用進行了攔截逾苫。下面以一個簡單的示例來說明java動態(tài)代理:
接口定義:

interface DoSomething {
    void doSomething();

    void doSomethingElse(String arg);
}

實現(xiàn)接口的類,也即被代理的類:

class RealObject implements DoSomething {

    @Override
    public void doSomething() {
        System.out.println("realObject doSomething");
    }

    @Override
    public void doSomethingElse(String arg) {
        System.out.println("realObject doSomethingElse " + arg);
    }

}

動態(tài)代理的處理類枚钓,需要實現(xiàn)InvocationHandler接口:

class DynamicProxyHander implements InvocationHandler {
    // 被代理類,將其作為實例變量在構(gòu)造函數(shù)中傳入
    private Object proxied;

    public DynamicProxyHander(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("***** proxy: " + proxy.getClass().getSimpleName() + "method :" + method + "args: " + args);
        if (null != args) {
            for (Object arg : args) {
                System.out.println(" " + arg);
            }
        }
        return method.invoke(proxied, args);
    }
}

調(diào)用RealObject和調(diào)用動態(tài)代理生成對象:

public class DynamicProxyDemo{
    public static void consume(DoSomething doSomething){
        doSomething.doSomething();
        doSomething.doSomethingElse("bonbo");
    }
    
    public static void main(String[] args) {
        RealObject real = new RealObject();
        consume(real);
        DoSomething proxy = (DoSomething)Proxy.newProxyInstance(DoSomething.class.getClassLoader(),
            new Class[]{DoSomething.class}, new DynamicProxyHander(real));
        consume(proxy);
    }
}

通過調(diào)用Proxy.newProxyInstance可以創(chuàng)建動態(tài)代理铅搓,這個方法需要一個類加載器,一個希望該代理實現(xiàn)的接口列表搀捷,以及InvocationHandler的一個實現(xiàn)星掰。動態(tài)代理可以將所有的調(diào)用重定向到調(diào)用處理器,因此通常會向調(diào)用處理器的構(gòu)造器傳遞一個實際對象的引用,從而使動態(tài)代理處理中介任務時蹋偏,可以將請求轉(zhuǎn)發(fā)便斥。

動態(tài)代理與字節(jié)碼生成技術

上面說的是動態(tài)代理的基本用法,相信許多java開發(fā)者都使用過動態(tài)代理威始,即使沒有直接 使用過java.lang.reflect.Proxy或?qū)崿F(xiàn)過InvocationHandler接口枢纠,也應該使用過spring做過Bean的管理。如果使用過Spring黎棠,那大多數(shù)情況下都會用過動態(tài)代理晋渺,因為如果bean是面向接口編程,那么在spring內(nèi)部都是用動態(tài)代理對bean進行增強的脓斩。動態(tài)代理中所謂的動態(tài)木西,是針對代碼實際編寫了代理的“靜態(tài)”而言的,動態(tài)代理的優(yōu)勢不在于減少了那一點的編碼量随静,而是實現(xiàn)了在原始類和接口未知的時候八千,就確定代理的代理行為,當代理類和原始類脫離直接聯(lián)系后燎猛,就可以和靈活的重用到不同的應用場景中恋捆。
在上述的代碼里,唯一的黑匣子就是Proxy.newProxyInstance方法重绷,除此之外并無任何特別之處沸停。這個方法返回了一個實現(xiàn)了DoSomething的接口。跟蹤這個方法的源碼:

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.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            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);
        }
    }

我們可以看到程序進行了驗證昭卓、優(yōu)化愤钾、緩存、同步候醒、生成字節(jié)碼能颁、顯式類加載等操作,最后調(diào)用了sun.misc.ProxyGenerator.generateProxyClass方法來完成生成字節(jié)碼的動作(上述源代碼只貼出了主方法倒淫,詳細步驟有興趣的讀者可以參閱java.lang.reflect.Proxy源代碼)伙菊。這個方法可以在運行時產(chǎn)生一個描述代理類的字節(jié)碼byte[]數(shù)組。大致的生成過程其實就是根據(jù).class文件的格式規(guī)范去拼裝字節(jié)碼昌简,但在實際開發(fā)中,直接生成字節(jié)碼的例子極為少見绒怨,如果有大量操作字節(jié)碼的需求纯赎,還是使用封裝好的字節(jié)碼類庫比較合適。(關于字節(jié)碼格式以及類加載過程南蹂,讀者可以自行查閱資料學習)

動態(tài)代理的運用

動態(tài)代理由于本身靈活的特性犬金,在Java技術棧中得到了非常多的運用。比如為java開發(fā)者所熟悉的spring框架,其AOP本質(zhì)上也是動態(tài)代理晚顷。包括hadoop RPC在內(nèi)的許多RPC框架也大量運用了動態(tài)代理峰伙,在日后的學習和實踐中,會多多關注所運用的工具和框架的實現(xiàn)機制该默,若有所感悟和收獲瞳氓,會記錄一下以供總結(jié)和分享。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末栓袖,一起剝皮案震驚了整個濱河市匣摘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌裹刮,老刑警劉巖音榜,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捧弃,居然都是意外死亡赠叼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門违霞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘴办,“玉大人,你說我怎么就攤上這事葛家』Т牵” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵癞谒,是天一觀的道長底燎。 經(jīng)常有香客問我,道長弹砚,這世上最難降的妖魔是什么双仍? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮桌吃,結(jié)果婚禮上朱沃,老公的妹妹穿的比我還像新娘。我一直安慰自己茅诱,他們只是感情好逗物,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瑟俭,像睡著了一般翎卓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上摆寄,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天失暴,我揣著相機與錄音坯门,去河邊找鬼。 笑死逗扒,一個胖子當著我的面吹牛古戴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矩肩,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼现恼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛮拔?” 一聲冷哼從身側(cè)響起述暂,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎建炫,沒想到半個月后畦韭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡肛跌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年艺配,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衍慎。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡转唉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出稳捆,到底是詐尸還是另有隱情赠法,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布乔夯,位于F島的核電站砖织,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏末荐。R本人自食惡果不足惜侧纯,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甲脏。 院中可真熱鬧眶熬,春花似錦、人聲如沸块请。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墩新。三九已至贸弥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抖棘,已是汗流浹背茂腥。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留切省,地道東北人最岗。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像朝捆,于是被迫代替她去往敵國和親般渡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容