一滋饲、引言
眾所周知厉碟,一旦提到AOP,相信大家都是條件反射的想到JDK代理和CGLib代理屠缭,沒(méi)錯(cuò)箍鼓,這兩個(gè)代理都是在運(yùn)行時(shí)內(nèi)存中臨時(shí)生成代理類,故而又稱作運(yùn)行時(shí)增強(qiáng)——?jiǎng)討B(tài)代理呵曹。世間萬(wàn)物都不是絕對(duì)的款咖,既然有動(dòng)態(tài)代理何暮,那么,是否有想過(guò):是不是存在靜態(tài)代理呢铐殃?
二海洼、LTW(Load Time Weaving)
其實(shí),除了運(yùn)行時(shí)織入切面的方式外富腊,我們還有一種途徑進(jìn)行切面織入坏逢,它可以在類加載期通過(guò)字節(jié)碼轉(zhuǎn)換,進(jìn)而將目標(biāo)織入切入點(diǎn)(目標(biāo)類)蟹肘,這種方式就是LTW词疼,即靜態(tài)代理(靜待代理也被稱作編譯時(shí)增強(qiáng),后面會(huì)有相關(guān)代碼樣例)帘腹。
LTW在Java5的時(shí)候就被引入了,想要了解其原理许饿,先要了解一個(gè)知識(shí)——Instrument包阳欲。
三、java.lang.instrument包的工作原理
JDK5.0時(shí)引入了此包陋率,目的就是為了能對(duì)JVM底層組建進(jìn)行訪問(wèn)球化。如何訪問(wèn)?其實(shí)說(shuō)來(lái)個(gè)人覺(jué)得還挺麻煩的瓦糟,就是需要通過(guò)JVM的啟動(dòng)參數(shù)-javaagent在啟動(dòng)時(shí)獲取JVM內(nèi)部組件的引用筒愚。參數(shù)格式如下:
-javaagent:<jarpath>[=options]此處先賣個(gè)關(guān)子,不急著解釋參數(shù)中的jarpath和options菩浙,后面的運(yùn)行代碼及結(jié)果的樣例中會(huì)進(jìn)行針對(duì)使用紅框標(biāo)記說(shuō)明巢掺,效果更好。
那么劲蜻,它和AOP有和關(guān)系呢陆淀?
因?yàn)樗贘VM啟動(dòng)時(shí)會(huì)裝配并應(yīng)用ClassTransformer,對(duì)類字節(jié)碼進(jìn)行轉(zhuǎn)換先嬉,進(jìn)而實(shí)現(xiàn)AOP的功能轧苫。
下面說(shuō)一下instrument包下的兩個(gè)重要接口:
ClassFileTransformer
它是Class文件轉(zhuǎn)換器接口,這個(gè)接口有且僅有一個(gè)方法疫蔓,如圖所示:
注意:transform方法會(huì)有一個(gè)返回值含懊,類型是byte[],表示轉(zhuǎn)換后的字節(jié)碼衅胀,但是如果返回為空岔乔,則表示不進(jìn)行節(jié)碼轉(zhuǎn)換處理,千萬(wàn)不要當(dāng)作是把原先類的字節(jié)碼清空拗小。
Instrumentation
這個(gè)接口提供了很多方法重罪,我們主要注意一個(gè)方法即可,即:addTransformer方法,它的作用就是把一些ClassFileTransformer注冊(cè)到JVM內(nèi)部剿配,接口如圖所示:
具體工作原理是這樣的:
① ClassFileTransformer實(shí)例注冊(cè)到JVM之后搅幅,JVM在加載Class文件時(shí),就會(huì)先調(diào)用ClassFileTransformer的transform()方法進(jìn)行字節(jié)碼轉(zhuǎn)換呼胚;
② 若注冊(cè)了多個(gè)ClassFileTransformer實(shí)例茄唐,則按照注冊(cè)時(shí)的順序進(jìn)行一次調(diào)用。
這樣也就實(shí)現(xiàn)了從JVM層面截獲字節(jié)碼蝇更,進(jìn)而織入操作者自己希望添加的邏輯沪编,即實(shí)現(xiàn)AOP效果。
四年扩、代碼及演示效果
說(shuō)了這么多蚁廓,來(lái)點(diǎn)干貨,下面用代碼給大家演示一下如何向JVM中注冊(cè)轉(zhuǎn)換器實(shí)現(xiàn)AOP的厨幻。為了方便大家閱讀相嵌,重要的說(shuō)明筆者已經(jīng)寫(xiě)在代碼的注釋上或者圖片空白處,大家注意查看况脆。
首先饭宾,我們實(shí)現(xiàn)一個(gè)自己的轉(zhuǎn)換器,用于模擬需要切入的功能
注意格了,這里再?gòu)?qiáng)調(diào)下看铆,代碼中的return null;并不是將加載類的字節(jié)碼置空。
其次盛末,我們?cè)賹?shí)現(xiàn)一個(gè)代理類
為什么要實(shí)現(xiàn)代理類內(nèi)弹惦,因?yàn)椴皇莿?dòng)態(tài)代理呀。满败。肤频。
最后,我們寫(xiě)一個(gè)主函數(shù)算墨,代表程序入口
到此為止宵荒,我們的Demo算是完成了,先來(lái)看一下運(yùn)行的結(jié)果:
五净嘀、打jar的時(shí)候需要注意的地方
大家看到執(zhí)行結(jié)果的截圖中报咳,cmd界面下運(yùn)行javaagent參數(shù)時(shí)指定了一個(gè)myTransformer.jar,這個(gè)jar是我們自己需要打出來(lái)的挖藏,可以直接使用eclipse具體步驟如下圖所示暑刃,注意圖中說(shuō)明:
六、總結(jié)
大家可以看到膜眠,其實(shí)使用此類代理并沒(méi)有動(dòng)態(tài)代理方便岩臣,甚至轉(zhuǎn)換器可能會(huì)對(duì)JVM所有類都產(chǎn)生影響溜嗜,操作起來(lái)更新相對(duì)麻煩,實(shí)際生產(chǎn)部署時(shí)會(huì)有很多不便架谎。
但是炸宵,寫(xiě)這些是為了讓大家更好、更多的去了解AOP谷扣,我們所熟知的AOP其實(shí)還有很多東西有待我們自身去學(xué)習(xí)和發(fā)現(xiàn)土全,其實(shí)Spring在"操作麻煩"這方面還是做了不少事的,提供了一些xml的配置化管理(此處就不再說(shuō)了会涎,因?yàn)楦杏X(jué)一說(shuō)又是一大長(zhǎng)篇裹匙,有興趣的大家可以自己去看看,多了解寫(xiě)東西總沒(méi)有壞處)末秃,很多情況下已經(jīng)不需要再配置javaagent參數(shù)了概页。
最后提一句,如果在面試中提到了這些练慕,相信面試官也會(huì)有加分吧绰沥。