Java 靜態(tài)代理 靜態(tài)代理通常用于對(duì)原有業(yè)務(wù)邏輯的擴(kuò)充。比如持有二方包的某個(gè)類,并調(diào)用了其中的某些方法。然后出于某種原因,比如記錄日志、打印方法執(zhí)行時(shí)間,但是又不好將這些邏輯寫(xiě)入二方包的方法里。所以可以創(chuàng)建一個(gè)代理類實(shí)現(xiàn)和二方方法相同的方法,通過(guò)讓代理類持有真實(shí)對(duì)象,然后在原代碼中調(diào)用代理類方法,來(lái)達(dá)到添加我們需要業(yè)務(wù)邏輯的目的。 這其實(shí)也就是代理模式的一種實(shí)現(xiàn),通過(guò)對(duì)真實(shí)對(duì)象的封裝,來(lái)實(shí)現(xiàn)擴(kuò)展性。 一個(gè)典型的代理模式通常有三個(gè)角色,這里稱之為**代理三要素** 共同接口
真實(shí)對(duì)象
代理對(duì)象
運(yùn)行代碼
這種代理模式也最為簡(jiǎn)單,就是通過(guò)proxy持有realObject的引用,并進(jìn)行一層封裝。 靜態(tài)代理的優(yōu)點(diǎn)和缺點(diǎn) 先看看代理模式的優(yōu)點(diǎn): 擴(kuò)展原功能,不侵入原代碼。 再看看這種代理模式的缺點(diǎn): 假如有這樣一個(gè)需求,有十個(gè)不同的RealObject,同時(shí)我們要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代碼可能是這樣的:
為了解決這個(gè)問(wèn)題,我們有方案一: 為這些方法創(chuàng)建不同的代理類,代理后的代碼是這樣的:
當(dāng)然,也有方案二: 通過(guò)創(chuàng)建一個(gè)proxy,持有不同的realObject,實(shí)現(xiàn)Action1、Action2、Action3接口,來(lái)讓代碼變成這樣:
于是你的代理模型會(huì)變成這樣: 毫無(wú)疑問(wèn),僅僅為了擴(kuò)展同樣的功能,在方案一種,我們會(huì)重復(fù)創(chuàng)建多個(gè)邏輯相同,僅僅RealObject引用不同的Proxy。 而在方案二中,會(huì)導(dǎo)致proxy的膨脹,而且這種膨脹往往是無(wú)意義的。此外,假如方法簽名是相同的,更需要在調(diào)用的時(shí)候引入額外的判斷邏輯。 java 動(dòng)態(tài)代理 搞清楚靜態(tài)代理的缺點(diǎn)十分重要,因?yàn)閯?dòng)態(tài)代理的目的就是為了解決靜態(tài)代理的缺點(diǎn)。通過(guò)使用動(dòng)態(tài)代理,我們可以通過(guò)在運(yùn)行時(shí),動(dòng)態(tài)生成一個(gè)持有RealObject、并實(shí)現(xiàn)代理接口的Proxy,同時(shí)注入我們相同的擴(kuò)展邏輯。哪怕你要代理的RealObject是不同的對(duì)象,甚至代理不同的方法,都可以動(dòng)過(guò)動(dòng)態(tài)代理,來(lái)擴(kuò)展功能。 簡(jiǎn)單理解,動(dòng)態(tài)代理就是我們上面提到的方案一,只不過(guò)這些proxy的創(chuàng)建都是自動(dòng)的并且是在運(yùn)行期生成的。 動(dòng)態(tài)代理基本用法 使用動(dòng)態(tài)代理,需要將要擴(kuò)展的功能寫(xiě)在一個(gè)InvocationHandler 實(shí)現(xiàn)類里:
這個(gè)Handler中的invoke方法中實(shí)現(xiàn)了代理類要擴(kuò)展的公共功能。 到這里,需要先看一下這個(gè)handler的用法:
Proxy.newProxyInstance 傳入的是一個(gè)ClassLoader, 一個(gè)代理接口,和我們定義的handler,返回的是一個(gè)Proxy的實(shí)例。 仔細(xì)體會(huì)這個(gè)過(guò)程,其實(shí)有點(diǎn)類似我們?cè)陟o態(tài)代理中提到的方案一,生成了一個(gè)包含我們擴(kuò)展功能,持有RealObject引用,實(shí)現(xiàn)Action接口的代理實(shí)例Proxy。只不過(guò)這個(gè)Proxy不是我們自己寫(xiě)的,而是java幫我們生成的,有沒(méi)有一點(diǎn)動(dòng)態(tài)的味道。 讓我們?cè)倩仡櫼幌麓砣兀赫鎸?shí)對(duì)象:RealObject,代理接口:Action,代理實(shí)例:Proxy 上面的代碼實(shí)含義也就是,輸入 RealObject、Action,返回一個(gè)Proxy。妥妥的代理模式。 綜上,動(dòng)態(tài)生成+代理模式,也就是動(dòng)態(tài)代理。 網(wǎng)上搜了不少文章,到了這里,接下來(lái)就是和cglib等動(dòng)態(tài)代理實(shí)現(xiàn)方法做一下橫向比較。本文不做橫向比較,為了不偏離主題,接下來(lái)做縱向挖掘。 看一下源碼 道理清楚了,但是這篇文章題目是搞懂,所以來(lái)看一下這個(gè)Proxy是如何自動(dòng)被生成的。入口就在newProxyInstance方法,核心代碼如下:
整體流程就是: 1、生成代理類Proxy的Class對(duì)象。 2、如果Class作用域?yàn)樗接?,通過(guò) setAccessible 支持訪問(wèn) 3、獲取Proxy Class構(gòu)造函數(shù),創(chuàng)建Proxy代理實(shí)例。 生成Proxy的Class文件 生成Class對(duì)象的方法中,先是通過(guò)傳進(jìn)來(lái)的ClassLoader參數(shù)和Class[] 數(shù)組作為組成鍵,維護(hù)了一個(gè)對(duì)于Proxy的Class對(duì)象的緩存。這樣需要相同Proxy的Class對(duì)象時(shí),只需要?jiǎng)?chuàng)建一次。 第一次創(chuàng)建該Class文件時(shí),為了線程安全,方法進(jìn)行了大量的處理,最后會(huì)來(lái)到ProxyClassFactory的apply方法中,經(jīng)過(guò)以下流程: 1、校驗(yàn)傳入的接口是否由傳入的ClassLoader加載的。 2、校驗(yàn)傳入是否是接口的Class對(duì)象。 3、校驗(yàn)是否傳入重復(fù)的接口。 4、拼裝代理類包名和類名,生成.class 文件的字節(jié)碼。 5、調(diào)用native方法,傳入字節(jié)碼,生成Class對(duì)象。
看一下第四步生成.class文件字節(jié)碼的過(guò)程,主要分為兩個(gè)階段:
第一個(gè)階段的代碼比較清晰,主要就是添加各種Method,比如toString()、equals,以及傳入的代理接口中的方法。再添加一下構(gòu)造方法以及靜態(tài)初始化方法。這要構(gòu)成了一個(gè)對(duì)象,存儲(chǔ)生成Proxy的Class的一些信息。 到了這里,已經(jīng)把要構(gòu)造的Proxy的方法基本定義完成了,接下來(lái)就要生成這個(gè).class文件了。
看到這個(gè)CAFEBABE,就清楚第二階段的內(nèi)容了。CAFEBABE是Class文件的魔數(shù),關(guān)于Class文件這個(gè)咖啡寶貝的魔數(shù),相信做Java的人都知道。沒(méi)錯(cuò),第二階段就是生成字節(jié)碼。按JVM規(guī)范,寫(xiě)入Class文件中包括權(quán)限控制、方法表、字段表等內(nèi)容,生成符合規(guī)范的Class文件。最后返回對(duì)應(yīng)的字節(jié)碼。 字節(jié)碼生成以后,通過(guò)調(diào)用native方法defineClass解析字節(jié)碼,就生成了Proxy的Class對(duì)象。 Proxy構(gòu)造方法 看一下Proxy的構(gòu)造方法字節(jié)碼生成部分:
關(guān)鍵在于,生成了一個(gè)參數(shù)為InvocationHandler的構(gòu)造方法,code加載的是jvm方法區(qū)中的代碼,然后通過(guò)invokespecial指令調(diào)用了父類構(gòu)造方法。 查看生成的Class文件 上面利用字節(jié)碼生成技術(shù)產(chǎn)生Class文件的過(guò)程,看起來(lái)可能比較晦澀,其實(shí)我們可以查看這個(gè)產(chǎn)生的Proxy到底是個(gè)什么樣子。 注意ProxyGenerator中有這樣一個(gè)邏輯:
再看一下saveGeneratedFiles這個(gè)變量:
這是一個(gè)final類型的變量,通過(guò)GetBooleanAction方法讀取系統(tǒng)變量,獲取系統(tǒng)設(shè)置。默認(rèn)這個(gè)值是false,稍微看一下System這個(gè)類的源碼,發(fā)現(xiàn)有可以設(shè)置系統(tǒng)變量的Api,然后在程序的main 函數(shù)設(shè)置一下這個(gè)變量: System.getProperties().setProperty('sun.misc.ProxyGenerator.saveGeneratedFiles', 'true'); 這個(gè)時(shí)候,再跑一遍程序,就可以看到生成的Proxy的Class文件了,直接雙擊利用 ide 反編譯。
省略一些無(wú)關(guān)代碼,可以看到兩個(gè)重要的方法。 一個(gè)就是我們的代理方法doSomething、另一個(gè)就是構(gòu)造方法。 這個(gè)$Proxy0 繼承 Proxy并調(diào)用了父類的構(gòu)造方法,回憶一下上文提到的invokeSpecial,怎么樣,對(duì)上了吧。 看一下Proxy中這個(gè)構(gòu)造方法:
在看一下$Proxy0 的代理方法: super.h.invoke(this, m3, (Object[])null); 再來(lái)回顧一下生成Proxy實(shí)例的過(guò)程:
其實(shí)newInstance生成Proxy實(shí)例時(shí),通過(guò)$Proxy0的Class對(duì)象,選擇了這個(gè)InvocationHandler為參數(shù)的構(gòu)造方法,傳入我們定義的InvocationHandler并生成了一個(gè) Proxy0的實(shí)例!InvocationHandler 里有realObject的邏輯以及我們的擴(kuò)展邏輯,當(dāng)我們調(diào)用Proxy0的doSomething方法時(shí),就會(huì)調(diào)用到我們InvocationHandler 里 實(shí)現(xiàn)的invoke方法。 對(duì)上面這個(gè)過(guò)程,做一張圖總結(jié)一下: |
|