Java帝國之動(dòng)態(tài)代理
1.深夜奏對(duì)
已經(jīng)快三更天了, Java帝國的國王還在看著IO大臣的奏章發(fā)呆,他有點(diǎn)想不明白, 帝國已經(jīng)給臣民了提供了這么多的東西,他們?yōu)槭裁催€不滿意呢? 集合、IO、反射、網(wǎng)絡(luò)、線程、泛型、JDBC ......在IT界哪一個(gè)不都是響當(dāng)當(dāng)?shù)挠餐ㄘ? 有了這些技術(shù),寫個(gè)Java程序多簡(jiǎn)單啊, 臣民們?yōu)楹芜€整天抗議呢?
這還是昨天IO大臣的一個(gè)奏章,其中說到各個(gè)部落要醞釀一場(chǎng)大規(guī)模的抗議游行,抗議Java不支持動(dòng)態(tài)性,不能在運(yùn)行時(shí)修改一個(gè)類,導(dǎo)致不能用聲明的方式來編程。
國王憤憤地想,我的政策太開明了,這些刁民不知好歹,蹬鼻子上臉,以后要堅(jiān)決加強(qiáng)東廠西廠錦衣衛(wèi)鎮(zhèn)撫司等紀(jì)檢法的建設(shè),有意見可以上訪, 不能這么胡鬧,增加社會(huì)不穩(wěn)定因素。帝國正在和Python, PHP等國家開戰(zhàn),處處都要銀子,攘外必先安內(nèi)啊。
想到這里,國王立刻命令呂公公宣IO大臣進(jìn)宮。
IO大臣半夜里被從熱騰騰的的被窩里拽出來,心里老大不情愿, 迷迷糊糊地跟著呂公公進(jìn)了宮。
“陛下半夜三更還在為國事操勞,真乃臣等之罪也 !” IO大臣雖然心里不情愿,但還是畢恭畢敬。
“愛卿,你說說這是怎么一回事? 什么是Java 不支持動(dòng)態(tài)性? ”國王拿出了奏章。
IO大臣心里明白了,原來是介個(gè)啊。
“啟奏陛下,其實(shí)這是刁民們羨慕Python 、Ruby 等語言的動(dòng)態(tài)性,想讓我們Java 也支持,他們最想要的一個(gè)功能就是能在運(yùn)行時(shí)對(duì)類進(jìn)行修改,這樣可以用聲明的方式來編程。”
“你能不能說點(diǎn)朕能聽懂的話?” 國王低沉的聲音里隱藏著馬上就要噴薄而出的怨氣,老子想了一晚上都沒整明白,你還在這里給我文縐縐的!
“是這樣” IO大臣開始調(diào)用腦細(xì)胞遣詞造句,準(zhǔn)備用通俗易懂的語言撲滅陛下的怒火。
“所謂運(yùn)行時(shí)對(duì)類進(jìn)行修改,打個(gè)比方來說,我寫了一個(gè)HelloWorld的類,其中有兩個(gè)方法:sayHello()和sayHelloToPHP(),陛下請(qǐng)看: ”
“這是帝國三歲小孩都能明白的代碼,說重點(diǎn)!”
“然后這個(gè)類運(yùn)行起來了,刁民們希望在運(yùn)行的時(shí)候可以修改這類, 譬如加一個(gè)新方法sayHelloToPython(), 或者對(duì)現(xiàn)在的sayHello()方法里加一點(diǎn)新東西, 甚至把sayHelloToPHP()這個(gè)方法刪除!”
“這些刁民太過分了, 難道他們不能寫個(gè)新的類來做這件事嗎?”
“陛下圣明, 臣也覺得可以新寫一個(gè)類比如HelloWorldNew來做這件事情,重新編譯一下不就行了嗎? 可是他們說的是在運(yùn)行時(shí)修改,是運(yùn)行時(shí),運(yùn)行時(shí),運(yùn)行時(shí),重要的事情說三遍,不是編譯時(shí)。”
“運(yùn)行時(shí)? 一個(gè)類一旦裝入到方法區(qū)還怎么修改 ” 國王還是很了解JVM這一套。 “你知道他們?yōu)槭裁从羞@個(gè)要求嗎?”
“他們說了想用聲明的方式來編程.....” IO大臣意識(shí)到大事不好。
“什么是聲明的方式” 國王窮追不舍
“這個(gè)臣還不太清楚......”
“快去徹查,限你三天回話。”
“遵旨”
2.明察暗訪
IO大臣冷汗都出來了, 他睡意全無,趕緊召集家丁幕僚準(zhǔn)備上山下鄉(xiāng)、明察暗訪,限他們兩天把這個(gè)“以聲明的方式編程”搞清楚。
兩天內(nèi)不斷有快馬回報(bào),各種各樣的信息如雪片般飛來。 IO大臣又花了一天時(shí)間整理,終于明白了這個(gè)“以聲明的方式編程”。
原來這幫刁民犯懶,寫完了代碼以后有這樣的需求:
在某些函數(shù)調(diào)用前后加上日志記錄
給某些函數(shù)加上事務(wù)的支持
給某些函數(shù)加上權(quán)限控制
......
這些需求挺通用的,如果在每個(gè)函數(shù)中都實(shí)現(xiàn)一遍,那重復(fù)代碼就太多了。 更要命的是有時(shí)候代碼是別人寫的,你只有class 文件,怎么修改? 怎么加上這些功能?
所以“刁民”們就想了一個(gè)損招,他們想在XML文件或者什么地方聲明一下, 比如對(duì)于添加日志的需求吧, 聲明的大意如下:
對(duì)于com.coderising這個(gè)package下所有以add開頭的方法,在執(zhí)行之前都要調(diào)用Logger.startLog()方法, 在執(zhí)行之后都要調(diào)用Logger.endLog()方法。
對(duì)于增加事務(wù)支持的需求,聲明的大意如下:
對(duì)于所有以DAO結(jié)尾的類,所有的方法執(zhí)行之前都要調(diào)用TransactionManager.begin(),執(zhí)行之后都要調(diào)用TransactionManager.commit(), 如果拋出異常的話調(diào)用TransactionManager.rollback()。
他們已經(jīng)充分發(fā)揮了自己的那點(diǎn)兒小聰明,號(hào)稱是開發(fā)了一個(gè)叫AOP的東西,能夠讀取這個(gè)XML中的聲明, 并且能夠找到那些需要插入日志的類和方法, 接下來就需要修改這些方法了。 但是Java帝國不允許修改一個(gè)已經(jīng)被加載或者正在運(yùn)行的類, 于是他們就不干了,就要抗議、就要游行,就要暴動(dòng), 真是可惡。
IO大臣決定向國王做一次匯報(bào),看看國王的反應(yīng)。
3.Java 動(dòng)態(tài)代理
國王不愧是國王, IO大臣稍微一解釋, 就明白怎么回事了。
“愛卿,你覺得該怎么辦? ” 皮球又被踢到了IO大臣那里。
“臣覺得不能讓這些刁民突破帝國的底線, 我們的class在運(yùn)行時(shí)是不能被修改的,如果也像Python,Ruby 那樣在運(yùn)行時(shí)可以肆意修改,那就太混亂了!” IO大臣小心翼翼地揣摩圣意。
“言之有理, 愛卿有何辦法? ”
“臣想到了一個(gè)辦法,雖然不能修改現(xiàn)有的類,但是可以在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建新的類啊,比如有個(gè)類HelloWorld:
“這么簡(jiǎn)單的類,怎么還得實(shí)現(xiàn)一個(gè)接口呢? ” 國王問道
“臣想給這些刁民們?cè)黾右稽c(diǎn)點(diǎn)障礙, 你不是想讓我動(dòng)態(tài)地創(chuàng)建新的類嗎?你必須得有接口才行啊” IO大臣又得意又陰險(xiǎn)地笑了。
國王臉上也露出了一絲不易覺察的微笑。
“現(xiàn)在他們的問題是要在sayHello()方法中調(diào)用Logger.startLog(), Logger.endLog()添加上日志, 但是這個(gè)sayHello()方法又不能修改了!”
“所以臣想了想, 可以動(dòng)態(tài)地生成一個(gè)新類,讓這個(gè)類作為HelloWorld的代理去做事情(加上日志功能), 陛下請(qǐng)看,這個(gè)HelloWorld代理也實(shí)現(xiàn)了IHelloWorld接口。 所以在調(diào)用方看來,都是IHelloWorld接口, 并不會(huì)意識(shí)到其實(shí)底層其實(shí)已經(jīng)滄海滄田了。”
“朕能明白你這個(gè)綠色的HelloWorld代理,但是你這個(gè)類怎么可能知道把Logger的方法加到什么地方呢?” 國王一下子看出了關(guān)鍵。
“陛下天資聰慧,臣拜服,‘刁民’們需要寫一個(gè)類來告訴我們具體把Logger的代碼加到什么地方, 這個(gè)類必須實(shí)現(xiàn)帝國定義的InvocationHandler接口,該接口中有個(gè)叫做invoke的方法就是他們寫擴(kuò)展代碼的地方。 比如這個(gè)LoggerHandler: ”
“ 看起來有些讓朕不舒服,不過朕大概明白了, 無非就是在調(diào)用真正的方法之前先調(diào)用Logger.startLog(), 在調(diào)用之后在調(diào)用Logger.end(), 這就是對(duì)方法進(jìn)行攔截了,對(duì)不對(duì)?”
“正是如此! 其實(shí)這個(gè)LoggerHandler 充當(dāng)了一個(gè)中間層, 我們自動(dòng)化生成的類$HelloWorld100會(huì)調(diào)用它,把sayHello這樣的方法調(diào)用傳遞給他 (上圖中的method變量),于是sayHello()方法就被添加上了Logger的startLog()和endLog()方法”
“此外,臣想提醒陛下的是,這個(gè)Handler不僅僅能作用于IHelloWorld 這個(gè)接口和 HelloWorld這個(gè)類,陛下請(qǐng)看,那個(gè)target 是個(gè)Object, 這就意味著任何類的實(shí)例都可以, 當(dāng)然我們會(huì)要求這些類必須得實(shí)現(xiàn)接口。 臣民們使用LoggerHandler的時(shí)候是這樣的:”
輸出:
Start Logging
Hello World
End Logging
“如果想對(duì)另外一個(gè)接口ICalculator和類Calcualtor做代理, 也可以復(fù)用這個(gè)LoggerHandler的類:”
“折騰了變天,原來魔法是在Proxy.newProxyInstance(....) 這里,就是動(dòng)態(tài)地生成了一個(gè)類嘛, 這個(gè)類對(duì)臣民們來說是動(dòng)態(tài)生成的, 也是看不到源碼的。”
“圣明無過陛下,我就是在運(yùn)行時(shí),在內(nèi)存中生成了一個(gè)新的類,這個(gè)類在調(diào)用sayHello() 或者add()方法的時(shí)候, 其實(shí)調(diào)用的是LoggerHanlder的invoke 方法, 而那個(gè)invoke就會(huì)攔截真正的方法調(diào)用,添加日志功能了! ”
“愛卿辛苦了,雖然有點(diǎn)繞,但是理解了還是挺簡(jiǎn)單的。 朕明天就頒發(fā)圣旨, 全國推行,對(duì)了你打算叫它什么名字? ”
“既然是在運(yùn)行時(shí)動(dòng)態(tài)的生成類,并且作為一個(gè)真實(shí)對(duì)象的代理來做事情, 那就叫動(dòng)態(tài)代理吧!”
動(dòng)態(tài)代理技術(shù)發(fā)布了,臣民們得到了暫時(shí)的安撫,但是這個(gè)動(dòng)態(tài)代理的缺陷就是必須有接口才能工作,帝國的臣民能忍受得了嗎?
【本文為51CTO專欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過作者微信公眾號(hào)coderising獲取授權(quán)】