Java 動態(tài)代理

先了解下代理模式的概念:為其他對象提供一種代理傻咖,以控制對這個對象的訪問它碎。也就是通過一個新的對象去代表目標(biāo)對象,再通過新對象間接去訪問目標(biāo)對象的功能棚菊,主要的作用有:

  • 增強(qiáng)目標(biāo)對象的功能
  • 保護(hù)目標(biāo)對象浸踩,屏蔽掉一些敏感的功能

具體的實(shí)現(xiàn)可以采用靜態(tài)代理或者動態(tài)代理兩種方案,我們增強(qiáng)目標(biāo)對象的功能為例來學(xué)習(xí)動態(tài)代理统求。

注意检碗,有些說法是“代理模式一般是內(nèi)部創(chuàng)建被代理的對象” ,你這例子是裝飾者模式码邻,但我認(rèn)為這并不是必須折剃,更多的時候需要根據(jù)業(yè)務(wù)去權(quán)衡,比如在某些場景上要求代理類有一定通用性像屋,目標(biāo)對象是可以依賴注入的怕犁,就需要變通了。

一己莺、靜態(tài)代理

首先定義一個Shop接口奏甫,作用就是售賣物品:

public interface Shop {
    void sale(String name);
}

再定義一個CoffeeShop類,實(shí)現(xiàn)Shop接口凌受,專門售賣咖啡:

public class CoffeeShop implements Shop {
    public void sale(String name) {
        System.out.println("開始制作" + name + "......制作完成阵子!");
    }
}

最后定義代理類,需要通過構(gòu)造函數(shù)注入目標(biāo)對象胜蛉,調(diào)用目標(biāo)對象的售賣方法款筑,并在售賣的前后添加問候語:

public class StaticProxy implements Shop {
    private Shop shop;

    public StaticProxy(Shop shop) {
        this.shop = shop;
    }

    public void sale(String name) {
        System.out.println("歡迎智蝠!");
        shop.sale(name);
        System.out.println("再見腾么!");
    }
}
public class ProxyTest {
    @Test
    public void staticProxyTest() {
        Shop shop = new CoffeeShop();
        StaticProxy staticProxy = new StaticProxy(shop);
        staticProxy.sale("拿鐵");
    }
}

測試結(jié)果如下:


動態(tài)代理我們主要學(xué)習(xí)兩種:JDK動態(tài)代理奈梳、CGLIB動態(tài)代理,在上邊的例子基礎(chǔ)上進(jìn)行改進(jìn)解虱。

二攘须、JDK動態(tài)代理

JDK動態(tài)代理主要依賴java.lang.reflect包下的相關(guān)類實(shí)現(xiàn),但要求被代理類必須最少實(shí)現(xiàn)一個接口于宙,來規(guī)定類要實(shí)現(xiàn)哪些方法捞魁,否則無法創(chuàng)建代理對象,所以可以繼續(xù)使用上邊的CoffeeShop類昆著。

public class JdkProxy {
    public Shop createProxy(final Shop shop) {
        ClassLoader loader = shop.getClass().getClassLoader();
        Class[] interfaces = shop.getClass().getInterfaces();
        InvocationHandler h = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("歡迎梧宫!");
                Object result = method.invoke(shop, args);
                System.out.println("再見塘匣!");
                return result;
            }
        };
        return (Shop) Proxy.newProxyInstance(loader, interfaces, h);
    }
}

可以看到跑揉,上邊的Proxy.newProxyInstance()方法就是用來創(chuàng)建代理對象的,需要三個參數(shù):

  • ClassLoader :一般我們使用目標(biāo)對象的類加載器。
  • Class[]:被代理的類實(shí)現(xiàn)接口的字節(jié)碼數(shù)組脱衙,保證代理對象和目標(biāo)對象有相同的方法退唠,要不然還怎么代理。這也是為什么要求被代理的類必須最少實(shí)現(xiàn)一個接口了。
  • InvocationHandler :一般可以用匿名內(nèi)部類,在其invoke()回調(diào)中完成目標(biāo)對象的功能增強(qiáng)硝枉。

invoke()方法三個參數(shù)的作用:

  • Object proxy:創(chuàng)建的代理對象璃诀。
  • Method method:當(dāng)前調(diào)用的代理對象的方法棕诵。
  • Object[] args:當(dāng)前調(diào)用的代理對象方法傳遞的參數(shù)牧抵。

這樣就可以通過反射間接調(diào)用目標(biāo)對象的對應(yīng)方法了:Object result = method.invoke(shop, args)

接下來就是測試代碼了:

public class ProxyTest {
    @Test
    public void jdkProxyTest() {
        Shop shop = new CoffeeShop();
        JdkProxy jdkProxy = new JdkProxy();
        Shop shopProxy = jdkProxy.createProxy(shop);
        shopProxy.sale("拿鐵");
    }
}

三、CGLIB動態(tài)代理

之前提到過蠢正,JDK動態(tài)代理要求被代理類必須最少實(shí)現(xiàn)一個接口,否則無法創(chuàng)建代理對象。但我們并不能要求用到的類都實(shí)現(xiàn)了接口持寄,所以就需要第三方的 CGLIB 庫來實(shí)現(xiàn)動態(tài)代理了,jar自行導(dǎo)入即可。下面用 CGLIB 來改造代碼:

public class CglibProxy {
    public Object createProxy(final Object shop) {
        Class clazz = shop.getClass();
        Callback callback = new MethodInterceptor() {
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("歡迎沧卢!");
                Object result = method.invoke(shop, args);
                System.out.println("再見!");
                return result;
            }
        };
        return Enhancer.create(clazz, callback);
    }
}

其實(shí)和 JDK動態(tài)代理的使用步驟類似但狭。

這里用Enhancer.create()方法來創(chuàng)建代理對象披诗,需要兩個參數(shù):

  • Class :被代理對象的字節(jié)碼。
  • Callback :這里用到了它的MethodInterceptor子類立磁,并在intercept()回調(diào)中完成目標(biāo)對象的功能增強(qiáng)呈队,這點(diǎn)和JDK動態(tài)代理類似。

通過如下測試代碼可以實(shí)現(xiàn)相同的效果:

public class ProxyTest {
    @Test
    public void cglibProxyTest() {
        CoffeeShop2 shop = new CoffeeShop2();
        CglibProxy cglibProxy = new CglibProxy();
        CoffeeShop2 shopProxy = (CoffeeShop2) cglibProxy.createProxy(shop);
        shopProxy.sale("拿鐵");
    }
}

其實(shí) CGLIB 庫也可以用來實(shí)現(xiàn)JDK動態(tài)代理例子中的功能唱歧,因?yàn)?code>Enhancer.create()還有其他的重載方法:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宪摧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子颅崩,更是在濱河造成了極大的恐慌几于,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挨摸,死亡現(xiàn)場離奇詭異孩革,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)得运,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門膝蜈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锅移,“玉大人,你說我怎么就攤上這事饱搏》翘辏” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵推沸,是天一觀的道長备绽。 經(jīng)常有香客問我,道長鬓催,這世上最難降的妖魔是什么肺素? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮宇驾,結(jié)果婚禮上倍靡,老公的妹妹穿的比我還像新娘。我一直安慰自己课舍,他們只是感情好塌西,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著筝尾,像睡著了一般捡需。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上筹淫,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天站辉,我揣著相機(jī)與錄音,去河邊找鬼贸街。 笑死庵寞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的薛匪。 我是一名探鬼主播捐川,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逸尖!你這毒婦竟也來了古沥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤娇跟,失蹤者是張志新(化名)和其女友劉穎岩齿,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苞俘,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盹沈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吃谣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乞封。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡做裙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肃晚,到底是詐尸還是另有隱情锚贱,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布关串,位于F島的核電站拧廊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏晋修。R本人自食惡果不足惜吧碾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望飞蚓。 院中可真熱鬧滤港,春花似錦、人聲如沸趴拧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽著榴。三九已至,卻和暖如春屁倔,著一層夾襖步出監(jiān)牢的瞬間脑又,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工锐借, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留问麸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓钞翔,卻偏偏與公主長得像严卖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子布轿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

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