JDK 動(dòng)態(tài)代理實(shí)現(xiàn)推演
我們已經(jīng)知道靜態(tài)代理的寫法,通過組合引入被代理對(duì)象,并在實(shí)現(xiàn)與被代理對(duì)象一致的服務(wù)接口中執(zhí)行被代理對(duì)象的方法,完成對(duì)被代理對(duì)象的環(huán)繞執(zhí)行。
如以下偽代碼:

接下來我們討論的問題是,如果讓我們來實(shí)現(xiàn)動(dòng)態(tài)代理,該怎么做?假設(shè)已知我們可以生成類代碼片段。
首先定義下這里的動(dòng)態(tài)代理,我們的設(shè)計(jì)目標(biāo)是能夠根據(jù)已有的被代理類,生成它的代理類代碼片段并加載,然后通過構(gòu)造函數(shù)創(chuàng)建代理對(duì)象。
我們假設(shè)有那么一個(gè)方法叫做 createProxy 用來生成代理類,它屬于誰現(xiàn)在并不重要。
以上圖中代碼為例,從特殊到一般地來思考。首先,對(duì)上面代理類的 listVideos ,我們的設(shè)計(jì)目標(biāo)是把它轉(zhuǎn)為右側(cè)的樣子,生成這樣具體的代碼片段是容易的;然后,我們的對(duì)這層轉(zhuǎn)換做一般化,生成某種代碼片段并執(zhí)行代理方法 listVideos。一般化意味著抽象與約定。我們往往會(huì)想到接口,不必著急,這不應(yīng)該在該層次的思考中涉及。
“計(jì)算機(jī)科學(xué)領(lǐng)域的任何問題都可以通過增加一個(gè)間接的中間層來解決”,如果能夠這么想,也奔著這個(gè)目標(biāo)來思考,問題往往會(huì)思索的更快;意識(shí)到這種思維并不是人人具備和理解,我們可以按照樸素的解題辦法來一步步得到問題的解。
特殊地,設(shè)想有一個(gè)的 createProxy 方法生成了代理類實(shí)例,它需要怎樣的步驟可以達(dá)成上面“特殊”的實(shí)現(xiàn)呢?
因?yàn)橐獙?shí)現(xiàn)對(duì)某個(gè)類的代理,也可能是具體到某個(gè)具體方法,假設(shè)一種形式的 createProxy 方法形如:createProxy(ThirdPartyTVClass tv, Method m)。在該方法中,要能夠生成類代碼片段并加載,代碼片段中有 listVideos 方法,其中的執(zhí)行語句正如右圖代理類的 listVideos 方法。最后 createProxy 方法要 返回一個(gè) ThirdPartyTVLib 的實(shí)例,以便在外部調(diào)用。
如果我們把上面的特殊話代碼一般化,我們需要抽象的思維方法。
第一,方法返回的類型應(yīng)該是某個(gè)接口,這里可以選擇與被代理類一致(我們往往會(huì)設(shè)計(jì)這樣的服務(wù)接口);第二,代理類執(zhí)行語句一般化,可以由某個(gè)對(duì)象管理,并適時(shí)引入到代理類的 listVideos,并且要求這個(gè)執(zhí)行語句要代理被代理類的 listVideos 方法。
建立在 createProxy(ThirdPartyTVClass tv, Method m) 方法上,第一個(gè)問題通過 ThirdPartyTVClass 的反射信息可以獲取和使用。?
第二個(gè)問題是關(guān)鍵,我們?cè)僖淮胃倪M(jìn) createProxy 方法,形如:createProxy(ThirdPartyTVClass, Method, Object),其中參數(shù) Object 負(fù)責(zé)管理執(zhí)行語句,我們想象可能是 Object.listVideosPlus ,并且在這個(gè)方法中一定要有被代理類的出現(xiàn)和執(zhí)行。
進(jìn)一步抽象。
我們可能并需要 listVideosPlus 這么一個(gè)挺具象的操作,? 那么對(duì) Object 的一類對(duì)象要求擁有一致的行為,并在 createProxy 生成的類代碼中適用,這里我們就能夠想到接口。
我們還是選擇 JDK 中的 InvocationHandler 接口來敘述,并不夾帶任何對(duì)它的認(rèn)知來重新思索實(shí)現(xiàn)。InvocationHandler? 接口中一般化的方法 invoke,應(yīng)該具有什么樣子的特征。以 createProxy(ThirdPartyTVClass tv, Method m, InvocationHandler inv) 為設(shè)計(jì)目標(biāo),生成的類代碼片段中,listVideos 方法體應(yīng)當(dāng)形如: inv.invoke(tv, m); 這樣把 tv(被代理對(duì)象)以參數(shù)引入并執(zhí)行。
在代理類中,也可以不關(guān)心具體的被代理類,但需要在執(zhí)行時(shí),使用具體的被代理類,如何實(shí)現(xiàn)呢?也就是說,在 InvocationHandler.invoke() 的實(shí)現(xiàn)中除了代理動(dòng)作,還需要執(zhí)行 被代理類的行為。把某個(gè)對(duì)象關(guān)聯(lián)起來也可以使用成員變量。因?yàn)椴魂P(guān)心被代理類類型,InvocationHandler 實(shí)現(xiàn)類中被代理的對(duì)象可以是 Object。對(duì) createProxy 的改進(jìn)形如:createProxy(Method, InvocationHandler ),那么把 被代理對(duì)象 和?
InvocationHandler 關(guān)聯(lián)起來成為當(dāng)下的設(shè)計(jì)問題。
其實(shí)多數(shù)情形考慮被代理類往往是作為代理類的成員變量(實(shí)現(xiàn)諸如延遲初始化),也是促成對(duì)對(duì) createProxy 的改進(jìn)的原因。關(guān)聯(lián) InvocationHandler 與被代理類,可以通過 InvocationHandler 如構(gòu)造函數(shù)綁定來實(shí)現(xiàn),最后 InvocationHandler 的 invoke 方法同步演進(jìn)為:InvocationHandler.invoke( Method)。
到此我們明確 createProxy(Method, InvocationHandler) 的執(zhí)行邏輯并生成代碼片段。
1. 生成代理類代碼片段,其實(shí)現(xiàn)了 listVideos 方法,方法中調(diào)用了 InvocationHandler.invoke(Method)
2. 根據(jù)代理類構(gòu)造器 創(chuàng)建代理對(duì)象,并把 InvocationHandler 對(duì)象作為構(gòu)造器參數(shù)傳入(注意:InvocationHandler 一定在創(chuàng)建代理類實(shí)例之前關(guān)聯(lián)了被代理對(duì)象,并且實(shí)現(xiàn)的 invoke 方法中被代理對(duì)象的方法也一定有執(zhí)行(真正代理邏輯所在,這里一定會(huì)有約定寫法))
3. 返回的代理對(duì)象可以調(diào)用 listVideos 方法,進(jìn)而執(zhí)行 InvocationHandler.invoke(Method) 方法,從而實(shí)現(xiàn)了代理方法的執(zhí)行
思考一些問題,為什么引入 InvocationHandler?答案該接口是對(duì)代理行為的一種抽象與約定,這種約定將來在 生成的代碼片段中發(fā)揮它的作用。可以不用這個(gè)對(duì)象嗎?完全沒問題,如果在生成的代碼片段中的 listVideos 舉例,其中的 具體執(zhí)行者就是 CachedTVClass。
從生成的代理類.class 的反編譯信息,順著結(jié)果反推實(shí)現(xiàn)也可以幫助理解動(dòng)態(tài)代理。