Java動(dòng)態(tài)代理 深度詳解(二)

如何使用動(dòng)態(tài)代理求厕?

參照上面的例子端衰,我們可以知道要實(shí)現(xiàn)動(dòng)態(tài)代理需要做兩方面的工作叠洗。

  • 首先需要新建一個(gè)類,并且這個(gè)類必須實(shí)現(xiàn) InvocationHandler 接口旅东。
//杏仁動(dòng)態(tài)代理
public class ApricotHandler implements InvocationHandler{

    private Object object;

    public ApricotHandler(Object object) {
        this.object = object;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object, args);    //調(diào)用真正的蛋糕機(jī)做蛋糕
        System.out.println("adding apricot...");
        return result;
    }
}
  • 在調(diào)用的時(shí)候使用 Proxy.newProxyInstance() 方法生成代理類灭抑。
public class CakeShop {
    public static void main(String[] args) {
        //水果蛋糕撒一層杏仁
        CakeMachine fruitCakeMachine = new FruitCakeMachine();
        ApricotHandler fruitCakeApricotHandler = new ApricotHandler(fruitCakeMachine);
        CakeMachine fruitCakeProxy = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
                fruitCakeMachine.getClass().getInterfaces(), fruitCakeApricotHandler);
        fruitCakeProxy.makeCake(); 
}
  • 最后直接使用生成的代理類調(diào)用相關(guān)的方法即可。

動(dòng)態(tài)代理的幾種實(shí)現(xiàn)方式

動(dòng)態(tài)代理其實(shí)指的是一種設(shè)計(jì)模式概念抵代,指的是通過代理來(lái)做一些通用的事情腾节,常見的應(yīng)用有權(quán)限系統(tǒng)、日志系統(tǒng)等荤牍,都用到了動(dòng)態(tài)代理案腺。

Java 動(dòng)態(tài)代理只是動(dòng)態(tài)代理的一種實(shí)現(xiàn)方式而已,動(dòng)態(tài)代理還有另外一種實(shí)現(xiàn)方式康吵,即

CGLib(Code Generation Library)劈榨。

Java 動(dòng)態(tài)代理只能針對(duì)實(shí)現(xiàn)了接口的類進(jìn)行拓展,所以細(xì)心的朋友會(huì)發(fā)現(xiàn)我們的代碼里有一個(gè)叫 MachineCake 的接口晦嵌。而 CGLib 則沒有這個(gè)限制同辣,因?yàn)?CGLib 是使用繼承原有類的方式來(lái)實(shí)現(xiàn)代理的。

我們還是舉個(gè)例子來(lái)說明

CGLib 是如何實(shí)現(xiàn)動(dòng)態(tài)代理的吧惭载。還是前面的例子:我們要做杏仁水果蛋糕旱函、巧克力水果蛋糕、五仁巧克力蛋糕描滔,這時(shí)候用代碼描述是這樣的棒妨。

首先我們需要寫一個(gè)杏仁攔截器類,這個(gè)攔截器可以給做好的蛋糕加上杏仁含长。

public class ApricotInterceptor implements MethodInterceptor {
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        methodProxy.invokeSuper(o, objects);
        System.out.println("adding apricot...");
        return o;
    }
}

接著直接讓蛋糕店使用 CGLib 提供的工具類做杏仁水果蛋糕:

public class CakeShop {
    public static void main(String[] args) { 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FruitCakeMachine.class);
        enhancer.setCallback(new ApricotInterceptor());
        FruitCakeMachine fruitCakeMachine = (FruitCakeMachine) enhancer.create();
        fruitCakeMachine.makeCake();
    }
}

上面的 enhancer.setSuperClass() 設(shè)置需要增強(qiáng)的類券腔,而 enhancer.setCallback() 則設(shè)置需要回調(diào)的攔截器,即實(shí)現(xiàn)了 MethodInterceptor 接口的類拘泞。最后最后使用 enhancer.create() 生成了對(duì)應(yīng)的增強(qiáng)類纷纫,最后輸出結(jié)果為:

making a Fruit Cake...
adding apricot...

和我們預(yù)期的一樣。如果要做一個(gè)杏仁巧克力蛋糕田弥,那么直接讓蛋糕店利用ApricotHandler 再做一個(gè)就可以了,它們的區(qū)別只是傳入的增強(qiáng)類不同铡原。

public class CakeShop {
    public static void main(String[] args) { 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ChocolateCakeMachine.class);
        enhancer.setCallback(new ApricotInterceptor());
        ChocolateCakeMachine chocolateCakeMachine = (ChocolateCakeMachine) enhancer.create();
        chocolateCakeMachine.makeCake();
    }
}

可以看到偷厦,這里傳入的增強(qiáng)類是 ChocolateCakeMachine,而不是之前的 FruitCakeMachine燕刻。

對(duì)比 Java 動(dòng)態(tài)代理和 CGLib 動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式只泼,你會(huì)發(fā)現(xiàn)

Java 動(dòng)態(tài)代理適合于那些有接口抽象的類代理,而 CGLib 則適合那些沒有接口抽象的類代理卵洗。

Java動(dòng)態(tài)代理的原理

從上面的例子我們可以知道请唱,Java 動(dòng)態(tài)代理的入口是從 Proxy.newInstance() 方法中開始的弥咪,那么我們就從這個(gè)方法開始邊剖析源碼邊理解其原理。

image

其實(shí)通過這個(gè)方法十绑,Java 替我們生成了一個(gè)繼承了指定接口(CakeMachine)的代理類(ApricotHandler)實(shí)例聚至。從

Proxy.newInstance()

的源碼我們可以看到首先調(diào)用了 getProxyClass0 方法,該方法返回了一個(gè) Class 實(shí)例對(duì)象本橙,該實(shí)例對(duì)象其實(shí)就是 ApricotHandler 的 Class 對(duì)象扳躬。接著獲取其構(gòu)造方法對(duì)象,最后生成該 Class 對(duì)象的實(shí)例甚亭。其實(shí)這里最主要的是 getProxyClass0() 方法贷币,這里面動(dòng)態(tài)生成了 ApricotHandler 的 Class 對(duì)象。下面我們就深入到 getProxyClass0() 方法中去了解這里面做了什么操作亏狰。

image

getProxyClass0() 方法首先是做了一些參數(shù)校驗(yàn)役纹,之后從 proxyClassCache 參數(shù)中取出 Class 對(duì)象。其實(shí) proxyClassCache 是一個(gè) Map 對(duì)象暇唾,緩存了所有動(dòng)態(tài)創(chuàng)建的 Class 對(duì)象促脉。從源碼中的注釋可以知道,如果從 Map 中取出的對(duì)象為空信不,那么其會(huì)調(diào)用

ProxyClassFactory

生成對(duì)應(yīng)的 Class 對(duì)象嘲叔。

image

在 ProxyClassFactory 類的源碼中,最終是調(diào)用了

ProxyGenerator.genrateProxyClass()

方法生成了對(duì)應(yīng)的 class 字節(jié)碼文件抽活。

到這里硫戈,我們已經(jīng)把動(dòng)態(tài)代理的 Java 源代碼都解析完了,現(xiàn)在思路就很清晰了下硕。Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

方法簡(jiǎn)單來(lái)說執(zhí)行了以下操作:

  • 1丁逝、生成一個(gè)實(shí)現(xiàn)了參數(shù) interfaces 里所有接口且繼承了 Proxy 的代理類的字節(jié)碼,然后用參數(shù)里的 classLoader 加載這個(gè)代理類梭姓。
  • 2霜幼、使用代理類父類的構(gòu)造函數(shù) Proxy(InvocationHandler h) 來(lái)創(chuàng)造一個(gè)代理類的實(shí)例,將我們自定義的 InvocationHandler 的子類傳入誉尖。
  • 3罪既、返回這個(gè)代理類實(shí)例炕贵,因?yàn)槲覀儤?gòu)造的代理類實(shí)現(xiàn)了 interfaces(也就是我們程序中傳入的 fruitCakeMachine.getClass().getInterfaces() 里的所有接口婿奔,因此返回的代理類可以強(qiáng)轉(zhuǎn)成 MachineCake 類型來(lái)調(diào)用接口中定義的方法。

CGLib動(dòng)態(tài)代理的原理

因?yàn)?JVM 并不允許在運(yùn)行時(shí)修改原有類糕殉,所以所有的動(dòng)態(tài)性都是通過新建類來(lái)實(shí)現(xiàn)的探熔,上面說到的 Java 動(dòng)態(tài)代理也不例外驹针。所以對(duì)于 CGLib 動(dòng)態(tài)代理的原理,其實(shí)也是通過動(dòng)態(tài)生成代理類诀艰,最后由代理類來(lái)完成操作實(shí)現(xiàn)的柬甥。

對(duì)于 CGLib 動(dòng)態(tài)代理的實(shí)現(xiàn)饮六,我并沒有深入到源碼中,而是通過查閱資料了解了其大概的實(shí)現(xiàn)原理苛蒲。

  • 首先卤橄,我們?cè)谑褂玫臅r(shí)候通過 enhancer.setSuperclass(FruitCakeMachine.class) 傳入了需要增加的類,CGLib 便會(huì)生成一個(gè)繼承了改類的代理類撤防。
  • 接著虽风,我們通過 enhancer.setCallback(new ApricotInterceptor()) 傳入了代理類對(duì)象,CGLib 通過組裝兩個(gè)類的結(jié)構(gòu)實(shí)現(xiàn)一個(gè)靜態(tài)代理寄月,從而達(dá)到具體的目的辜膝。

而在 CGLib 生成新類的過程中,其使用的是一個(gè)名為 ASM 的東西漾肮,它對(duì) Java 的 class 文件進(jìn)行操作厂抖、生成新的 class 文件。如果你對(duì) CGLib 的原理感興趣克懊,不妨看看這篇文章:從兄弟到父子:動(dòng)態(tài)代理在民間是怎么玩的忱辅?

動(dòng)態(tài)代理的應(yīng)用

動(dòng)態(tài)代理在代碼界可是有非常重要的意義,我們開發(fā)用到的許多框架都使用到了這個(gè)概念谭溉。我所知道的就有:Spring AOP墙懂、Hibernate、Struts 使用到了動(dòng)態(tài)代理扮念。

  • Spring AOP损搬。Spring 最重要的一個(gè)特性是 AOP(Aspect Oriented Programming 面向切面編程),利用 Spring AOP 可以快速地實(shí)現(xiàn)權(quán)限校驗(yàn)柜与、安全校驗(yàn)等公用操作巧勤。而 Spring AOP 的原理則是通過動(dòng)態(tài)代理實(shí)現(xiàn)的,默認(rèn)情況下 Spring AOP 會(huì)采用 Java 動(dòng)態(tài)代理實(shí)現(xiàn)弄匕,而當(dāng)該類沒有對(duì)應(yīng)接口時(shí)才會(huì)使用 CGLib 動(dòng)態(tài)代理實(shí)現(xiàn)颅悉。
  • Hibernate。Hibernate 是一個(gè)常用的 ORM 層框架迁匠,在獲取數(shù)據(jù)時(shí)常用的操作有:get() 和 load() 方法剩瓶,它們的區(qū)別是:get() 方法會(huì)直接獲取數(shù)據(jù),而 load() 方法則會(huì)延遲加載城丧,等到用戶真的去取數(shù)據(jù)的時(shí)候才利用代理類去讀數(shù)據(jù)庫(kù)延曙。
  • Struts。Struts 現(xiàn)在雖然因?yàn)槠涮?bug 已經(jīng)被拋棄芙贫,但是曾經(jīng)用過 Struts 的人都知道 Struts 中的攔截器搂鲫。攔截器有非常強(qiáng)的 AOP 特性傍药,仔細(xì)了解之后你會(huì)發(fā)現(xiàn) Struts 攔截器其實(shí)也是用動(dòng)態(tài)代理實(shí)現(xiàn)的磺平。

總結(jié)

我們通過蛋糕店的不同業(yè)務(wù)場(chǎng)景介紹了靜態(tài)代理和動(dòng)態(tài)代理的應(yīng)用魂仍,接著重點(diǎn)介紹了動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式(Java 動(dòng)態(tài)代理、CGLib 動(dòng)態(tài)代理)的使用方法及其實(shí)現(xiàn)原理拣挪,其中還針對(duì) Java 動(dòng)態(tài)代理的源碼進(jìn)行了簡(jiǎn)單的分析擦酌。最后,我們介紹了動(dòng)態(tài)代理在實(shí)際上編程中的應(yīng)用(Spring AOP菠劝、Hibernate赊舶、Struts)。

歡迎加入學(xué)習(xí)交流群569772982赶诊,大家一起學(xué)習(xí)交流

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笼平,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子舔痪,更是在濱河造成了極大的恐慌寓调,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锄码,死亡現(xiàn)場(chǎng)離奇詭異夺英,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)滋捶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門痛悯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人重窟,你說我怎么就攤上這事载萌。” “怎么了亲族?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵炒考,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我霎迫,道長(zhǎng)斋枢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任知给,我火速辦了婚禮瓤帚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涩赢。我一直安慰自己戈次,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布筒扒。 她就那樣靜靜地躺著怯邪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪花墩。 梳的紋絲不亂的頭發(fā)上悬秉,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天澄步,我揣著相機(jī)與錄音,去河邊找鬼和泌。 笑死村缸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的武氓。 我是一名探鬼主播梯皿,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼县恕!你這毒婦竟也來(lái)了东羹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤忠烛,失蹤者是張志新(化名)和其女友劉穎百姓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體况木,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垒拢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了火惊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片求类。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖屹耐,靈堂內(nèi)的尸體忽然破棺而出尸疆,到底是詐尸還是另有隱情,我是刑警寧澤惶岭,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布寿弱,位于F島的核電站,受9級(jí)特大地震影響按灶,放射性物質(zhì)發(fā)生泄漏症革。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一鸯旁、第九天 我趴在偏房一處隱蔽的房頂上張望噪矛。 院中可真熱鬧,春花似錦铺罢、人聲如沸艇挨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缩滨。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脉漏,已是汗流浹背蛋勺。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸠删,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓贼陶,卻偏偏與公主長(zhǎng)得像刃泡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子碉怔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法烘贴,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法撮胧,繼承相關(guān)的語(yǔ)法桨踪,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 31,631評(píng)論 18 399
  • 從三月份找實(shí)習(xí)到現(xiàn)在芹啥,面了一些公司锻离,掛了不少,但最終還是拿到小米墓怀、百度汽纠、阿里、京東傀履、新浪虱朵、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,246評(píng)論 11 349
  • 今天是AlphaGo對(duì)戰(zhàn)李世石的的最后一場(chǎng)比賽钓账,隨著5場(chǎng)比賽的即將落幕和“阿爾法狗”的完美勝利碴犬,讓國(guó)人開始更加期待...
    檸檬烏冬面閱讀 1,262評(píng)論 0 2
  • 零用錢 兒子之前收到幾百元的紅包,放到他自己的錢包里梆暮。上完財(cái)商課后服协,我決定對(duì)兒子的零用錢進(jìn)行管理。于是和兒子約定:...
    ivisliu閱讀 497評(píng)論 0 0
  • 有時(shí)候啦粹,不寫點(diǎn)東西還真意識(shí)不到“腦子里以為正常的句子怎么會(huì)這么殘疾蚯涮?!”而且卖陵,即使自己明明以前遇到過這種情況遭顶,在看...
    去往風(fēng)口的豬閱讀 156評(píng)論 0 0