給女同事講完代理后,女同事說:你好棒哦
說在前面:今天我們來聊一聊 Java 中的代理,先來聊聊故事背景:
❝小明想購買法國某個(gè)牌子的香水送給女朋友,但是在國內(nèi)沒有貨源售賣,親自去法國又大費(fèi)周章了,而小紅現(xiàn)在正在法國玩耍,她和小明是好朋友,可以幫小明買到這個(gè)牌子的香水,于是小明就找到小紅,答應(yīng)給她多加 5% 的辛苦費(fèi),小紅答應(yīng)了,小明成功在中國買到了法國的香水。之后小紅開啟了瘋狂的代購模式,賺到了很多手續(xù)費(fèi)。❞
在故事中,「小明是一個(gè)客戶」,它讓小紅幫忙購買香水,「小紅就成了一個(gè)代理對(duì)象」,而「香水提供商是一個(gè)真實(shí)的對(duì)象」,可以售賣香水,小明通過代理商小紅,購買到法國的香水,這就是一個(gè)代購的例子。我畫了一幅圖幫助理解這個(gè)故事的整個(gè)結(jié)構(gòu)。
這個(gè)故事是最典型的代理模式,代購從供應(yīng)商購買貨物后返回給調(diào)用者,也就是需要代理的小明。
代理可以分為靜態(tài)代理和動(dòng)態(tài)代理兩大類:
「靜態(tài)代理」
- 優(yōu)點(diǎn):代碼結(jié)構(gòu)簡(jiǎn)單,較容易實(shí)現(xiàn)
- 缺點(diǎn):無法適配所有代理場(chǎng)景,如果有新的需求,需要修改代理類,「不符合軟件工程的開閉原則」
小紅現(xiàn)在只是代理香水,如果小明需要找小紅買法國紅酒,那小紅就需要代理法國紅酒了,但是靜態(tài)代理去擴(kuò)展代理功能「必須修改小紅內(nèi)部的邏輯,這會(huì)讓小紅內(nèi)部代碼越來越臃腫」,后面會(huì)詳細(xì)分析。
「動(dòng)態(tài)代理」
- 優(yōu)點(diǎn):能夠動(dòng)態(tài)適配特定的代理場(chǎng)景,擴(kuò)展性較好,「符合軟件工程的開閉原則」
- 缺點(diǎn):動(dòng)態(tài)代理需要利用到反射機(jī)制和動(dòng)態(tài)生成字節(jié)碼,導(dǎo)致其性能會(huì)比靜態(tài)代理稍差一些,「但是相比于優(yōu)點(diǎn),這些劣勢(shì)幾乎可以忽略不計(jì)」
如果小明需要找小紅代理紅酒,我們「無需修改代理類小紅的內(nèi)部邏輯」,只需要關(guān)注擴(kuò)展的功能點(diǎn):「代理紅酒」,實(shí)例化新的類,通過一些轉(zhuǎn)換即可讓小紅「既能夠代理香水也能夠代理紅酒」了。
本文將會(huì)通過以下幾點(diǎn),盡可能讓你理解 Java 代理中所有重要的知識(shí)點(diǎn):
學(xué)習(xí)代理模式(實(shí)現(xiàn)故事的代碼,解釋代理模式的類結(jié)構(gòu)特點(diǎn))
比較靜態(tài)代理與動(dòng)態(tài)代理二者的異同
Java 中常見的兩種動(dòng)態(tài)代理實(shí)現(xiàn)(JDK Proxy 和 Cglib)
動(dòng)態(tài)代理的應(yīng)用(Spring AOP)
代理模式
(1)我們定義好一個(gè)「售賣香水」的接口,定義好售賣香水的方法并傳入該香水的價(jià)格。
- public interface SellPerfume {
- void sellPerfume(double price);
- }
(2)定義香奈兒(Chanel)香水提供商,實(shí)現(xiàn)接口。
- public class ChanelFactory implements SellPerfume {
- @Override
- public void sellPerfume(double price) {
- System.out.println("成功購買香奈兒品牌的香水,價(jià)格是:" + price + "元");
- }
- }
(3)定義「小紅」代理類,她需要代購去售賣香奈兒香水,所以她是香奈兒香水提供商的代理對(duì)象,同樣實(shí)現(xiàn)接口,并在內(nèi)部保存對(duì)目標(biāo)對(duì)象(香奈兒提供商)的引用,控制其它對(duì)象對(duì)目標(biāo)對(duì)象的訪問。
- public class XiaoHongSellProxy implements SellPerfume {
- private SellPerfume sellPerfumeFactory;
- public XiaoHongSellProxy(SellPerfume sellPerfumeFactory) {
- this.sellPerfumeFactory = sellPerfumeFactory;
- }
- @Override
- public void sellPerfume(double price) {
- doSomethingBeforeSell(); // 前置增強(qiáng)
- sellPerfumeFactory.sellPerfume(price);
- doSomethingAfterSell(); // 后置增強(qiáng)
- }
- private void doSomethingBeforeSell() {
- System.out.println("小紅代理購買香水前的額外操作...");
- }
- private void doSomethingAfterSell() {
- System.out.println("小紅代理購買香水后的額外操作...");
- }
- }
(4)小明是一個(gè)需求者,他需要去購買香水,只能通過小紅去購買,所以他去找小紅購買1999.99的香水。
- public class XiaoMing {
- public static void main(String[] args) {
- ChanelFactory factory = new ChanelFactory();
- XiaoHongSellProxy proxy = new XiaoHongSellProxy(factory);
- proxy.sellPerfume(1999.99);
- }
- }
我們來看看運(yùn)行結(jié)果,小紅在向小明售賣香水前可以執(zhí)行額外的其它操作,如果良心點(diǎn)的代購就會(huì)「打折、包郵···」,如果黑心點(diǎn)的代購就會(huì)「加手續(xù)費(fèi)、售出不退還···」,是不是很刺激。
我們來看看上面 4 個(gè)類組成的類圖關(guān)系結(jié)構(gòu),可以發(fā)現(xiàn)「小紅」和「香奈兒提供商」都實(shí)現(xiàn)了「售賣香水」這一接口,而小紅內(nèi)部增加了對(duì)提供商的引用,用于調(diào)用提供商的售賣香水功能。
實(shí)現(xiàn)代理模式,需要走以下幾個(gè)步驟:
- 「定義真實(shí)對(duì)象和代理對(duì)象的公共接口」(售賣香水接口)
- 「代理對(duì)象內(nèi)部保存對(duì)真實(shí)目標(biāo)對(duì)象的引用」(小紅引用提供商)
- 訪問者僅能通過代理對(duì)象訪問真實(shí)目標(biāo)對(duì)象,「不可直接訪問目標(biāo)對(duì)象」(小明只能通過小紅去購買香水,不能直接到香奈兒提供商購買)
❝代理模式很容易產(chǎn)生錯(cuò)誤思維的一個(gè)地方:代理對(duì)象并不是真正提供服務(wù)的一個(gè)對(duì)象,它只是替訪問者訪問目標(biāo)對(duì)象的一個(gè)「中間人」,真正提供服務(wù)的還是目標(biāo)對(duì)象,而代理對(duì)象的作用就是在目標(biāo)對(duì)象提供服務(wù)之前和之后能夠執(zhí)行額外的邏輯。
從故事來說,小紅并不是真正賣香水的,賣香水的還是香奈兒提供商,而小紅只不過是在讓香奈兒賣香水之前和之后執(zhí)行了一些自己額外加上去的操作。❞
講完這個(gè)代理模式的代碼實(shí)現(xiàn),我們來系統(tǒng)地學(xué)習(xí)它究竟是如何定義的,以及實(shí)現(xiàn)它需要注意什么規(guī)范。
代理模式的定義:「給目標(biāo)對(duì)象提供一個(gè)代理對(duì)象,代理對(duì)象包含該目標(biāo)對(duì)象,并控制對(duì)該目標(biāo)對(duì)象的訪問?!?/p>
代理模式的目的:
- 通過代理對(duì)象的隔離,可以在對(duì)目標(biāo)對(duì)象訪問前后「增加額外的業(yè)務(wù)邏輯,實(shí)現(xiàn)功能增強(qiáng)?!?/li>
- 通過代理對(duì)象訪問目標(biāo)對(duì)象,可以「防止系統(tǒng)大量地直接對(duì)目標(biāo)對(duì)象進(jìn)行不正確地訪問」,出現(xiàn)不可預(yù)測(cè)的后果
靜態(tài)代理與動(dòng)態(tài)代理
你是否會(huì)有我一樣的疑惑:代理為什么還要分靜態(tài)和動(dòng)態(tài)的?它們兩個(gè)有啥不同嗎?
很明顯,所有人都會(huì)有這樣的疑惑,我們先來看看它們的相同點(diǎn):
- 都能夠?qū)崿F(xiàn)代理模式(這不廢話嗎...)
- 無論是靜態(tài)代理還是動(dòng)態(tài)代理,代理對(duì)象和目標(biāo)對(duì)象都需要實(shí)現(xiàn)一個(gè)「公共接口」
重點(diǎn)當(dāng)然是它們的不同之處,動(dòng)態(tài)代理在靜態(tài)代理的基礎(chǔ)上做了改進(jìn),極大地提高了程序的「可維護(hù)性」和「可擴(kuò)展性」。我先列出它們倆的不同之處,再詳細(xì)解釋為何靜態(tài)代理不具備這兩個(gè)特性:
- 動(dòng)態(tài)代理產(chǎn)生代理對(duì)象的時(shí)機(jī)是「運(yùn)行時(shí)動(dòng)態(tài)生成」,它沒有 Java 源文件,「直接生成字節(jié)碼文件實(shí)例化代理對(duì)象」;而靜態(tài)代理的代理對(duì)象,在「程序編譯時(shí)」已經(jīng)寫好 Java 文件了,直接 new 一個(gè)代理對(duì)象即可。
- 動(dòng)態(tài)代理比靜態(tài)代理更加穩(wěn)健,對(duì)程序的可維護(hù)性和可擴(kuò)展性更加友好
目前來看,代理對(duì)象小紅已經(jīng)能夠代理購買香水了,但有一天,小紅的另外一個(gè)朋友小何來了,「他想購買最純正的法國紅酒」,國內(nèi)沒有這樣的購買渠道,小紅剛巧也在法國,于是小何就想找小紅幫他買紅酒啦,這和小明找小紅是一個(gè)道理的,都是想讓小紅做代理。
但問題是:在程序中,小紅只能代理購買香水,「如果要代理購買紅酒」,要怎么做呢?
- 創(chuàng)建售賣紅酒的接口
- 售賣紅酒提供商和代理對(duì)象小紅都需要實(shí)現(xiàn)該接口
- 小何訪問小紅,讓小紅賣給他紅酒
OK,事已至此,代碼就不重復(fù)寫了,我們來探討一下,面對(duì)這種新增的場(chǎng)景,上面的這種實(shí)現(xiàn)方法有沒有什么缺陷呢?
我們不得不提的是軟件工程中的「開閉原則」
❝開閉原則:在編寫程序的過程中,軟件的所有對(duì)象應(yīng)該是對(duì)擴(kuò)展是開放的,而對(duì)修改是關(guān)閉的❞
靜態(tài)代理違反了開閉原則,原因是:面對(duì)新的需求時(shí),需要修改代理類,增加實(shí)現(xiàn)新的接口和方法,導(dǎo)致代理類越來越龐大,變得難以維護(hù)。
雖然說目前代理類只是實(shí)現(xiàn)了2個(gè)接口,**如果日后小紅不只是代理售賣紅酒,還需要代理售賣電影票、代購日本壽司······**實(shí)現(xiàn)的接口會(huì)變得越來越多,內(nèi)部的結(jié)構(gòu)變得越來越復(fù)雜,「整個(gè)類顯得愈發(fā)臃腫」,變得不可維護(hù),之后的擴(kuò)展也會(huì)成問題,只要任意一個(gè)接口有改動(dòng),就會(huì)牽扯到這個(gè)代理類,維護(hù)的代價(jià)很高。
「所以,為了提高類的可擴(kuò)展性和可維護(hù)性,滿足開閉原則,Java 提供了動(dòng)態(tài)代理機(jī)制?!?/p>
常見的動(dòng)態(tài)代理實(shí)現(xiàn)
動(dòng)態(tài)代理最重要的當(dāng)然是「動(dòng)態(tài)」兩個(gè)字,學(xué)習(xí)動(dòng)態(tài)代理的過程,最重要的就是理解何為動(dòng)態(tài),話不多說,馬上開整。
我們來明確一點(diǎn):「動(dòng)態(tài)代理解決的問題是面對(duì)新的需求時(shí),不需要修改代理對(duì)象的代碼,只需要新增接口和真實(shí)對(duì)象,在客戶端調(diào)用即可完成新的代理?!?/p>
這樣做的目的:滿足軟件工程的開閉原則,提高類的可維護(hù)性和可擴(kuò)展性。
JDK Proxy
JDK Proxy 是 JDK 提供的一個(gè)動(dòng)態(tài)代理機(jī)制,它涉及到兩個(gè)核心類,分別是Proxy和InvocationHandler,我們先來了解如何使用它們。
以小紅代理賣香水的故事為例,香奈兒香水提供商依舊是真實(shí)對(duì)象,實(shí)現(xiàn)了SellPerfume接口,這里不再重新寫了,重點(diǎn)是「小紅代理」,這里的代理對(duì)象不再是小紅一個(gè)人,而是一個(gè)「代理工廠」,里面會(huì)有許多的代理對(duì)象。我畫了一幅圖,你看了之后會(huì)很好理解:
小明來到代理工廠,需要購買一款法國在售的香奈兒香水,那么工廠就會(huì)**找一個(gè)可以實(shí)際的代理對(duì)象(動(dòng)態(tài)實(shí)例化)**分配給小明,例如小紅或者小花,讓該代理對(duì)象完成小明的需求。「該代理工廠含有無窮無盡的代理對(duì)象可以分配,且每個(gè)對(duì)象可以代理的事情可以根據(jù)程序的變化而動(dòng)態(tài)變化,無需修改代理工廠?!?/p>
如果有一天小明需要招待一個(gè)可以「代購紅酒」的代理對(duì)象,該代理工廠依舊可以滿足他的需求,無論日后需要什么代理,都可以滿足,是不是覺得很神奇?我們來學(xué)習(xí)如何使用它。
我們看一下動(dòng)態(tài)代理的 UML 類圖結(jié)構(gòu)長(zhǎng)什么樣子。
可以看到和靜態(tài)代理區(qū)別不大,唯一的變動(dòng)是代理對(duì)象,我做了標(biāo)注:「由代理工廠生產(chǎn)」。
這句話的意思是:「代理對(duì)象是在程序運(yùn)行過程中,由代理工廠動(dòng)態(tài)生成,代理對(duì)象本身不存在 Java 源文件」。
那么,我們的關(guān)注點(diǎn)有2個(gè):
- 如何實(shí)現(xiàn)一個(gè)代理工廠
- 如何通過代理工廠動(dòng)態(tài)生成代理對(duì)象
首先,代理工廠需要實(shí)現(xiàn)InvocationHanlder接口并實(shí)現(xiàn)其invoke()方法。
- public class SellProxyFactory implements InvocationHandler {
- /** 代理的真實(shí)對(duì)象 */
- private Object realObject;
- public SellProxyFactory(Object realObject) {
- this.realObject = realObject;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- doSomethingBefore();
- Object obj = method.invoke(realObject, args);
- doSomethingAfter();
- return obj;
- }
- private void doSomethingAfter() {
- System.out.println("執(zhí)行代理后的額外操作...");
- }
- private void doSomethingBefore() {
- System.out.println("執(zhí)行代理前的額外操作...");
- }
- }
invoke() 方法有3個(gè)參數(shù):
- Object proxy:代理對(duì)象
- Method method:真正執(zhí)行的方法
- Object[] agrs:調(diào)用第二個(gè)參數(shù) method 時(shí)傳入的參數(shù)列表值
invoke() 方法是一個(gè)代理方法,也就是說最后客戶端請(qǐng)求代理時(shí),執(zhí)行的就是該方法。代理工廠類到這里為止已經(jīng)結(jié)束了,我們接下來看第二點(diǎn):「如何通過代理工廠動(dòng)態(tài)生成代理對(duì)象」。
生成代理對(duì)象需要用到Proxy類,它可以幫助我們生成任意一個(gè)代理對(duì)象,里面提供一個(gè)靜態(tài)方法newProxyInstance。
- Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
實(shí)例化代理對(duì)象時(shí),需要傳入3個(gè)參數(shù):
- ClassLoader loader:加載動(dòng)態(tài)代理類的類加載器
- Class[] interfaces:代理類實(shí)現(xiàn)的接口,可以傳入多個(gè)接口
- InvocationHandler h:指定代理類的「調(diào)用處理程序」,即調(diào)用接口中的方法時(shí),會(huì)找到該代理工廠h,執(zhí)行invoke()方法
我們?cè)诳蛻舳苏?qǐng)求代理時(shí),就需要用到上面這個(gè)方法。
- public class XiaoMing {
- public static void main(String[] args) {
- ChanelFactory chanelFactory = new ChanelFactory();
- SellProxyFactory sellProxyFactory = new SellProxyFactory(chanelFactory);
- SellPerfume sellPerfume = (SellPerfume) Proxy.newProxyInstance(chanelFactory.getClass().getClassLoader(),
- chanelFactory.getClass().getInterfaces(),
- sellProxyFactory);
- sellPerfume.sellPerfume(1999.99);
- }
- }
執(zhí)行結(jié)果和靜態(tài)代理的結(jié)果相同,但二者的思想是不一樣的,一個(gè)是靜態(tài),一個(gè)是動(dòng)態(tài)。那又如何體現(xiàn)出動(dòng)態(tài)代理的優(yōu)勢(shì)呢?別急,往下看就知道了。
❝注意看下圖,相比靜態(tài)代理的前置增強(qiáng)和后置增強(qiáng),少了「小紅」二字,實(shí)際上代理工廠分配的代理對(duì)象是隨機(jī)的,不會(huì)針對(duì)某一個(gè)具體的代理對(duì)象,所以每次生成的代理對(duì)象都不一樣,也就不確定是不是小紅了,但是能夠唯一確定的是,「這個(gè)代理對(duì)象能和小紅一樣幫小明買到香水!」❞
按照之前的故事線發(fā)展,小紅去代理紅酒,而「小明又想買法國的名牌紅酒」,所以去找代理工廠,讓它再分配一個(gè)人幫小明買紅酒,代理工廠說:“當(dāng)然沒問題!我們是專業(yè)的!等著!”
我們需要實(shí)現(xiàn)兩個(gè)類:紅酒提供商類 和 售賣紅酒接口。
- /** 售賣紅酒接口 */
- public interface SellWine {
- void sellWine(double price);
- }
- /** 紅酒供應(yīng)商 */
- public class RedWineFactory implements SellWine {
- @Override
- public void sellWine(double price) {
- System.out.println("成功售賣一瓶紅酒,價(jià)格:" + price + "元");
- }
- }
然后我們的小明在請(qǐng)求代理工廠時(shí),就可以「實(shí)例化一個(gè)可以售賣紅酒的代理」了。
- public class XiaoMing {
- public static void main(String[] args) {
- // 實(shí)例化一個(gè)紅酒銷售商
- RedWineFactory redWineFactory = new RedWineFactory();
- // 實(shí)例化代理工廠,傳入紅酒銷售商引用控制對(duì)其的訪問
- SellProxyFactory sellProxyFactory = new SellProxyFactory(redWineFactory);
- // 實(shí)例化代理對(duì)象,該對(duì)象可以代理售賣紅酒
- SellWine sellWineProxy = (SellWine) Proxy.newProxyInstance(redWineFactory.getClass().getClassLoader(),
- redWineFactory.getClass().getInterfaces(),
- sellProxyFactory);
- // 代理售賣紅酒
- sellWineProxy.sellWine(1999.99);
- }
- }
期待一下執(zhí)行結(jié)果,你會(huì)很驚喜地發(fā)現(xiàn),居然也能夠代理售賣紅酒了,但是我們「沒有修改代理工廠」。
回顧一下我們新增紅酒代理功能時(shí),需要2個(gè)步驟:
- 創(chuàng)建新的紅酒提供商SellWineFactory和售賣紅酒接口SellWine在客戶端實(shí)例化一個(gè)代理對(duì)象,然后向該代理對(duì)象購買紅酒
- 再回想「開閉原則:面向擴(kuò)展開放,面向修改關(guān)閉」。動(dòng)態(tài)代理正是滿足了這一重要原則,在面對(duì)功能需求擴(kuò)展時(shí),只需要關(guān)注擴(kuò)展的部分,不需要修改系統(tǒng)中原有的代碼。
如果感興趣想深究的朋友,把注意力放在Proxy.newProxyInstance()這個(gè)方法上,這是整個(gè) JDK 動(dòng)態(tài)代理起飛的一個(gè)方法。
講到這里,JDK 提供的動(dòng)態(tài)代理已經(jīng)到尾聲了,我們來總結(jié)一下 JDK 的動(dòng)態(tài)代理:
(1)JDK 動(dòng)態(tài)代理的使用方法
- 代理工廠需要實(shí)現(xiàn) InvocationHandler接口,調(diào)用代理方法時(shí)會(huì)轉(zhuǎn)向執(zhí)行invoke()方法
- 生成代理對(duì)象需要使用Proxy對(duì)象中的newProxyInstance()方法,返回對(duì)象可強(qiáng)轉(zhuǎn)成傳入的其中一個(gè)接口,然后調(diào)用接口方法即可實(shí)現(xiàn)代理
(2)JDK 動(dòng)態(tài)代理的特點(diǎn)
目標(biāo)對(duì)象強(qiáng)制需要實(shí)現(xiàn)一個(gè)接口,否則無法使用 JDK 動(dòng)態(tài)代理
- 「(以下為擴(kuò)展內(nèi)容,如果不想看可跳過)」
Proxy.newProxyInstance() 是生成動(dòng)態(tài)代理對(duì)象的關(guān)鍵,我們可來看看它里面到底干了些什么,我把重要的代碼提取出來,一些對(duì)分析無用的代碼就省略掉了。
- private static final Class<?>[] constructorParams ={ InvocationHandler.class };
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces,
- InvocationHandler h) {
- // 獲取代理類的 Class 對(duì)象
- Class<?> cl = getProxyClass0(loader, intfs);
- // 獲取代理對(duì)象的顯示構(gòu)造器,參數(shù)類型是 InvocationHandler
- final Constructor<?> cons = cl.getConstructor(constructorParams);
- // 反射,通過構(gòu)造器實(shí)例化動(dòng)態(tài)代理對(duì)象
- return cons.newInstance(new Object[]{h});
- }
我們看到第 6 行獲取了一個(gè)動(dòng)態(tài)代理對(duì)象,那么是如何生成的呢?接著往下看。
- private static Class<?> getProxyClass0(ClassLoader loader,
- Class<?>... interfaces) {
- // 去代理類對(duì)象緩存中獲取代理類的 Class 對(duì)象
- return proxyClassCache.get(loader, interfaces);
- }
發(fā)現(xiàn)里面用到一個(gè)緩存 「proxyClassCache」,從結(jié)構(gòu)來看類似于是一個(gè) map結(jié)構(gòu),根據(jù)類加載器loader和真實(shí)對(duì)象實(shí)現(xiàn)的接口interfaces查找是否有對(duì)應(yīng)的 Class 對(duì)象,我們接著往下看 get() 方法。
- public V get(K key, P parameter) {
- // 先從緩存中查詢是否能根據(jù) key 和 parameter 查詢到 Class 對(duì)象
- // ...
- // 生成一個(gè)代理類
- Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
- }
在 get() 方法中,如果沒有從緩存中獲取到 Class 對(duì)象,則需要利用「subKeyFactory」 去實(shí)例化一個(gè)動(dòng)態(tài)代理對(duì)象,而在 「Proxy」 類中包含一個(gè) 「ProxyClassFactory」 內(nèi)部類,由它來創(chuàng)建一個(gè)動(dòng)態(tài)代理類,所以我們接著去看 ProxyClassFactory 中的 apply() 方法。
- private static final class ProxyClassFactory
- implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
- // 非常重要,這就是我們看到的動(dòng)態(tài)代理的對(duì)象名前綴!
- private static final String proxyClassNamePrefix = "$Proxy";
- @Override
- public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
- Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
- // 一些狀態(tài)校驗(yàn)
- // 計(jì)數(shù)器,該計(jì)數(shù)器記錄了當(dāng)前已經(jīng)實(shí)例化多少個(gè)代理對(duì)象
- long num = nextUniqueNumber.getAndIncrement();
- // 動(dòng)態(tài)代理對(duì)象名拼接!包名 + "$Proxy" + 數(shù)字
- String proxyName = proxyPkg + proxyClassNamePrefix + num;
- // 生成字節(jié)碼文件,返回一個(gè)字節(jié)數(shù)組
- byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
- proxyName, interfaces, accessFlags);
- try {
- // 利用字節(jié)碼文件創(chuàng)建該字節(jié)碼的 Class 類對(duì)象
- return defineClass0(loader, proxyName,
- proxyClassFile, 0, proxyClassFile.length);
- } catch (ClassFormatError e) {
- throw new IllegalArgumentException(e.toString());
- }
- }
- }
apply() 方法中注意有「兩個(gè)非常重要的方法」:
- 「ProxyGenerator.generateProxyClass()」:它是生成字節(jié)碼文件的方法,它返回了一個(gè)字節(jié)數(shù)組,字節(jié)碼文件本質(zhì)上就是一個(gè)字節(jié)數(shù)組,所以 proxyClassFile數(shù)組就是一個(gè)字節(jié)碼文件
- 「defineClass0()」:生成字節(jié)碼文件的 Class 對(duì)象,它是一個(gè) native 本地方法,調(diào)用操作系統(tǒng)底層的方法創(chuàng)建類對(duì)象
而 proxyName 是代理對(duì)象的名字,我們可以看到它利用了「proxyClassNamePrefix + 計(jì)數(shù)器」 拼接成一個(gè)新的名字。所以在 DEBUG 時(shí),停留在代理對(duì)象變量上,你會(huì)發(fā)現(xiàn)變量名是$Proxy0。
到了這里,源碼分析完了,是不是感覺被掏空了?哈哈哈哈,其實(shí)我當(dāng)時(shí)也有這種感覺,不過現(xiàn)在你也感覺到,JDK 的動(dòng)態(tài)代理其實(shí)并不是特別復(fù)雜吧(只要你有毅力)
CGLIB
CGLIB(Code generation Library) 不是 JDK 自帶的動(dòng)態(tài)代理,它需要導(dǎo)入第三方依賴,它是一個(gè)字節(jié)碼生成類庫,能夠在運(yùn)行時(shí)動(dòng)態(tài)生成代理類對(duì) 「Java類 和 Java接口」 擴(kuò)展。
CGLIB不僅能夠?yàn)?Java接口 做代理,而且「能夠?yàn)槠胀ǖ?Java類 做代理」,而 JDK Proxy 「只能為實(shí)現(xiàn)了接口」的 Java類 做代理,所以 CGLIB 為 Java 的代理做了很好的擴(kuò)展。「如果需要代理的類沒有實(shí)現(xiàn)接口,可以選擇 Cglib 作為實(shí)現(xiàn)動(dòng)態(tài)代理的工具。」
廢話太多,一句話概括:「CGLIB 可以代理沒有實(shí)現(xiàn)接口的 Java 類」
下面我們來學(xué)習(xí)它的使用方法,以「小明找代理工廠買法國香水」這個(gè)故事背景為例子。
(1)導(dǎo)入依賴
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib-nodep</artifactId>
- <version>3.3.0</version>
- <scope>test</scope>
- </dependency>
❝還有另外一個(gè) CGLIB 包,二者的區(qū)別是帶有-nodep的依賴內(nèi)部已經(jīng)包括了ASM字節(jié)碼框架的相關(guān)代碼,無需額外依賴ASM❞
(2)CGLIB 代理中有兩個(gè)核心的類:MethodInterceptor接口 和 Enhancer類,前者是實(shí)現(xiàn)一個(gè)代理工廠的根接口,后者是創(chuàng)建動(dòng)態(tài)代理對(duì)象的類,在這里我再貼一次故事的結(jié)構(gòu)圖,幫助你們理解。
首先我們來定義代理工廠SellProxyFactory。
- public class SellProxyFactory implements MethodInterceptor {
- // 關(guān)聯(lián)真實(shí)對(duì)象,控制對(duì)真實(shí)對(duì)象的訪問
- private Object realObject;
- /** 從代理工廠中獲取一個(gè)代理對(duì)象實(shí)例,等價(jià)于創(chuàng)建小紅代理 */
- public Object getProxyInstance(Object realObject) {
- this.realObject = realObject;
- Enhancer enhancer = new Enhancer();
- // 設(shè)置需要增強(qiáng)類的類加載器
- enhancer.setClassLoader(realObject.getClass().getClassLoader());
- // 設(shè)置被代理類,真實(shí)對(duì)象
- enhancer.setSuperclass(realObject.getClass());
- // 設(shè)置方法攔截器,代理工廠
- enhancer.setCallback(this);
- // 創(chuàng)建代理類
- return enhancer.create();
- }
- @Override
- public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
- doSomethingBefore(); // 前置增強(qiáng)
- Object object = methodProxy.invokeSuper(o, objects);
- doSomethingAfter(); // 后置增強(qiáng)
- return object;
- }
- private void doSomethingBefore() {
- System.out.println("執(zhí)行方法前額外的操作...");
- }
- private void doSomethingAfter() {
- System.out.println("執(zhí)行方法后額外的操作...");
- }
- }
intercept() 方法涉及到 4 個(gè)參數(shù):
- Object o:被代理對(duì)象
- Method method:被攔截的方法
- Object[] objects:被攔截方法的所有入?yún)⒅?/li>
- MethodProxy methodProxy:方法代理,用于調(diào)用原始的方法
對(duì)于 methodProxy 參數(shù)調(diào)用的方法,在其內(nèi)部有兩種選擇:invoke() 和 invokeSuper() ,二者的區(qū)別不在本文展開說明,感興趣的讀者可以參考本篇文章:Cglib源碼分析 invoke和invokeSuper的差別
在 getInstance() 方法中,利用 Enhancer 類實(shí)例化代理對(duì)象(可以看作是小紅)返回給調(diào)用者小明,即可完成代理操作。
- public class XiaoMing {
- public static void main(String[] args) {
- SellProxyFactory sellProxyFactory = new SellProxyFactory();
- // 獲取一個(gè)代理實(shí)例
- SellPerfumeFactory proxyInstance =
- (SellPerfumeFactory) sellProxyFactory.getProxyInstance(new SellPerfumeFactory());
- // 創(chuàng)建代理類
- proxyInstance.sellPerfume(1999.99);
- }
- }
我們關(guān)注點(diǎn)依舊放在可擴(kuò)展性和可維護(hù)性上,Cglib 依舊符合「開閉原則」,如果小明需要小紅代理購買紅酒,該如何做呢?這里礙于篇幅原因,我不再將完整的代碼貼出來了,可以自己試著手動(dòng)實(shí)現(xiàn)一下,或者在心里有一個(gè)大概的實(shí)現(xiàn)思路即可。
我們來總結(jié)一下 CGLIB 動(dòng)態(tài)代理:
(1)CGLIB 的使用方法:
- 代理工廠需要「實(shí)現(xiàn) MethodInterceptor 接口」,并重寫方法,「內(nèi)部關(guān)聯(lián)真實(shí)對(duì)象」,控制第三者對(duì)真實(shí)對(duì)象的訪問;代理工廠內(nèi)部暴露 getInstance(Object realObject) 方法,「用于從代理工廠中獲取一個(gè)代理對(duì)象實(shí)例」。
- Enhancer 類用于從代理工廠中實(shí)例化一個(gè)代理對(duì)象,給調(diào)用者提供代理服務(wù)。
JDK Proxy 和 CGLIB 的對(duì)比
(2)仔細(xì)對(duì)比一下,JDK Proxy 和 CGLIB 具有相似之處:
JDK Proxy | CGLIB | |
---|---|---|
代理工廠實(shí)現(xiàn)接口 | InvocationHandler | MethodInterceptor |
構(gòu)造代理對(duì)象給 Client 服務(wù) | Proxy | Enhancer |
二者都是用到了兩個(gè)核心的類,它們也有不同:
- 最明顯的不同:CGLIB 可以代理「大部分類」(第二點(diǎn)說到);而 JDK Proxy 「僅能夠代理實(shí)現(xiàn)了接口的類」
- CGLIB 采用動(dòng)態(tài)創(chuàng)建被代理類的子類實(shí)現(xiàn)方法攔截,子類內(nèi)部重寫被攔截的方法,所以 CGLIB 不能代理被 final 關(guān)鍵字修飾的類和方法
細(xì)心的讀者會(huì)發(fā)現(xiàn),講的東西都是「淺嘗輒止」(你都沒有給我講源碼,水文實(shí)錘),動(dòng)態(tài)代理的精髓在于「程序在運(yùn)行時(shí)動(dòng)態(tài)生成代理類對(duì)象,攔截調(diào)用方法,在調(diào)用方法前后擴(kuò)展額外的功能」,而生成動(dòng)態(tài)代理對(duì)象的原理就是「反射機(jī)制」,在上一篇文章中,我詳細(xì)講到了如何利用反射實(shí)例化對(duì)象,調(diào)用方法······在代理中運(yùn)用得淋漓盡致,所以反射和代理也是天生的一對(duì),談到其中一個(gè),必然會(huì)涉及另外一個(gè)。
動(dòng)態(tài)代理的實(shí)際應(yīng)用
傳統(tǒng)的 OOP 編程符合從上往下的編碼關(guān)系,卻不符合從左往右的編碼關(guān)系,如果你看不懂,可以參考下面的動(dòng)圖,OOP 滿足我們一個(gè)方法一個(gè)方法從上往下地執(zhí)行,但是卻不能「從左往右嵌入代碼」,而 AOP 的出現(xiàn)很好地彌補(bǔ)了這一點(diǎn),它「允許我們將重復(fù)的代碼邏輯抽取出來形成一個(gè)單獨(dú)的覆蓋層」,在執(zhí)行代碼時(shí)可以將該覆蓋層毫無知覺的嵌入到原代碼邏輯里面去。
Spring AOP
如下圖所示,method1 和 method2 都需要在方法執(zhí)行前后「記錄日志」,實(shí)際上會(huì)有更多的方法需要記錄日志,傳統(tǒng)的 OOP 只能夠讓我們?cè)诿總€(gè)方法前后手動(dòng)記錄日志,大量的Log.info存在于方法內(nèi)部,導(dǎo)致代碼閱讀性下降,方法內(nèi)部無法專注于自己的邏輯。
「AOP 可以將這些重復(fù)性的代碼包裝到額外的一層,監(jiān)聽方法的執(zhí)行,當(dāng)方法被調(diào)用時(shí),通用的日志記錄層會(huì)攔截掉該方法,在該方法調(diào)用前后記錄日志,這樣可以讓方法專注于自己的業(yè)務(wù)邏輯而無需關(guān)注其它不必要的信息。」
2
Spring AOP 有許多功能:提供緩存、提供日志環(huán)繞、事務(wù)處理······在這里,我會(huì)以「事務(wù)」作為例子向你講解 Spring 底層是如何使用動(dòng)態(tài)代理的。
Spring 的事務(wù)涉及到一個(gè)核心注解@Transactional,相信很多人在項(xiàng)目中都用到過,加上這個(gè)注解之后,在執(zhí)行方法時(shí)如果發(fā)生異常,該方法內(nèi)所有的事務(wù)都回滾,否則全部提交生效,這是最宏觀的表現(xiàn),它內(nèi)部是如何實(shí)現(xiàn)的呢?今天就來簡(jiǎn)單分析一下。
每個(gè)有關(guān)數(shù)據(jù)庫的操作都要保證一個(gè)事務(wù)內(nèi)的所有操作,要么全部執(zhí)行成功,要么全部執(zhí)行失敗,傳統(tǒng)的事務(wù)失敗回滾和成功提交是使用try...catch代碼塊完成的
- SqlSession session = null;
- try{
- session = getSqlSessionFactory().openSession(false);
- session.update("...", new Object());
- // 事務(wù)提交
- session.commit();
- }catch(Exception e){
- // 事務(wù)回滾
- session.rollback();
- throw e;
- }finally{
- // 關(guān)閉事務(wù)
- session.close();
- }
如果多個(gè)方法都需要寫這一段邏輯非常冗余,所以 Spring 給我們封裝了一個(gè)注解 @Transactional,使用它后,調(diào)用方法時(shí)會(huì)監(jiān)視方法,如果方法上含有該注解,就會(huì)自動(dòng)幫我們把數(shù)據(jù)庫相關(guān)操作的代碼包裹起來,最終形成類似于上面的一段代碼原理,當(dāng)然這里并不準(zhǔn)確,只是給你們一個(gè)大概的總覽,了解Spring AOP 的本質(zhì)在干什么,這篇文章講解到這里,知識(shí)量應(yīng)該也非常多了,好好消化上面的知識(shí)點(diǎn),為后面的 Spring AOP 專題學(xué)習(xí)打下堅(jiān)實(shí)的基礎(chǔ)。
本文轉(zhuǎn)載自微信公眾號(hào)「 Java建設(shè)者」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 Java建設(shè)者公眾號(hào)。