動態代理之實現(父子)
1、深夜奏對
已經快三更天了, Java帝國的國王還在看著IO大臣的奏章發呆,他有點想不明白, 帝國已經給臣民了提供了這麼多的東西,他們為什麼還不滿意呢? 集合、IO、反射、網路、執行緒、泛型、JDBC ......在IT界哪一個不都是響噹噹的硬通貨? 有了這些技術,寫個Java程式多簡單啊, 臣民們為何還整天抗議呢?
這還是昨天IO大臣的一個奏章,其中說到各個部落要醞釀一場大規模的抗議遊行,抗議Java不支援動態性,不能在執行時修改一個類,導致不能用宣告的方式來程式設計。
國王憤憤地想,我的政策太開明瞭,這些刁民不知好歹,蹬鼻子上臉,以後要堅決加強東廠西廠錦衣衛鎮撫司等紀檢法的建設,有意見可以上訪,
想到這裡,國王立刻命令呂公公宣IO大臣進宮。
IO大臣半夜裡被從熱騰騰的的被窩裡拽出來,心裡老大不情願, 迷迷糊糊地跟著呂公公進了宮。
“陛下半夜三更還在為國事操勞,真乃臣等之罪也 !” IO大臣雖然心裡不情願,但還是畢恭畢敬。
“愛卿,你說說這是怎麼一回事? 什麼是Java 不支援動態性? ”國王拿出了奏章。
IO大臣心裡明白了,原來是介個啊。
“啟奏陛下,其實這是刁民們羨慕Python 、Ruby 等語言的動態性,想讓我們Java 也支援,他們最想要的一個功能就是能在執行時對類進行修改,這樣可以用宣告的方式來程式設計。”
“你能不能說點朕能聽懂的話?” 國王低沉的聲音裡隱藏著馬上就要噴薄而出的怨氣,老子想了一晚上都沒整明白,你還在這裡給我文縐縐的!
“是這樣” IO大臣開始呼叫腦細胞遣詞造句,準備用通俗易懂的語言撲滅陛下的怒火。
“所謂執行時對類進行修改,打個比方來說,我寫了一個HelloWorld的類,其中有兩個方法:sayHello()和sayHelloToPHP(),陛下請看: ”
“這是帝國三歲小孩都能明白的程式碼,說重點!”
“然後這個類執行起來了,刁民們希望在執行的時候可以修改這類, 譬如加一個新方法sayHelloToPython(), 或者對現在的sayHello()方法里加一點新東西, 甚至把sayHelloToPHP()這個方法刪除!”
“這些刁民太過分了, 難道他們不能寫個新的類來做這件事嗎?”
“陛下聖明, 臣也覺得可以新寫一個類比如HelloWorldNew來做這件事情,重新編譯一下不就行了嗎? 可是他們說的是在執行時修改,是執行時,執行時,執行時,重要的事情說三遍,不是編譯時。”
“執行時? 一個類一旦裝入到方法區還怎麼修改 ” 國王還是很瞭解JVM這一套。 “你知道他們為什麼有這個要求嗎?”
“他們說了想用宣告的方式來程式設計.....” IO大臣意識到大事不好。
“什麼是宣告的方式” 國王窮追不捨
“這個臣還不太清楚......”
“快去徹查,限你三天回話。”
“遵旨”
2、明察暗訪
IO大臣冷汗都出來了, 他睡意全無,趕緊召集家丁幕僚準備上山下鄉、明察暗訪,限他們兩天把這個“以宣告的方式程式設計”搞清楚。
兩天內不斷有快馬回報,各種各樣的資訊如雪片般飛來。 IO大臣又花了一天時間整理,終於明白了這個“以宣告的方式程式設計”。
原來這幫刁民犯懶,寫完了程式碼以後有這樣的需求:
在某些函式呼叫前後加上日誌記錄
給某些函式加上事務的支援
給某些函式加上許可權控制
......
這些需求挺通用的,如果在每個函式中都實現一遍,那重複程式碼就太多了。 更要命的是有時候程式碼是別人寫的,你只有class 檔案,怎麼修改? 怎麼加上這些功能?
所以“刁民”們就想了一個損招,他們想在XML檔案或者什麼地方宣告一下, 比如對於新增日誌的需求吧, 宣告的大意如下:
對於com.coderising這個package下所有以add開頭的方法,在執行之前都要呼叫Logger.startLog()方法, 在執行之後都要呼叫Logger.endLog()方法。
對於增加事務支援的需求,宣告的大意如下:
對於所有以DAO結尾的類,所有的方法執行之前都要呼叫TransactionManager.begin(),執行之後都要呼叫TransactionManager.commit(), 如果丟擲異常的話呼叫TransactionManager.rollback()。
他們已經充分發揮了自己的那點兒小聰明,號稱是開發了一個叫AOP的東西,能夠讀取這個XML中的宣告, 並且能夠找到那些需要插入日誌的類和方法, 接下來就需要修改這些方法了。 但是Java帝國不允許修改一個已經被載入或者正在執行的類, 於是他們就不幹了,就要抗議、就要遊行,就要暴動, 真是可惡。
IO大臣決定向國王做一次彙報,看看國王的反應。
3、Java 動態代理
國王不愧是國王, IO大臣稍微一解釋, 就明白怎麼回事了。
“愛卿,你覺得該怎麼辦? ” 皮球又被踢到了IO大臣那裡。
“臣覺得不能讓這些刁民突破帝國的底線, 我們的class在執行時是不能被修改的,如果也像Python,Ruby 那樣在執行時可以肆意修改,那就太混亂了!” IO大臣小心翼翼地揣摩聖意。
“言之有理, 愛卿有何辦法? ”
“臣想到了一個辦法,雖然不能修改現有的類,但是可以在執行時動態的建立新的類啊,比如有個類HelloWorld:
“這麼簡單的類,怎麼還得實現一個介面呢? ” 國王問道
“臣想給這些刁民們增加一點點障礙, 你不是想讓我動態地建立新的類嗎?你必須得有接口才行啊” IO大臣又得意又陰險地笑了。
國王臉上也露出了一絲不易覺察的微笑。
“現在他們的問題是要在sayHello()方法中呼叫Logger.startLog(), Logger.endLog()新增上日誌, 但是這個sayHello()方法又不能修改了!”
“所以臣想了想, 可以動態地生成一個新類,讓這個類作為HelloWorld的代理去做事情(加上日誌功能), 陛下請看,這個HelloWorld代理也實現了IHelloWorld介面。 所以在呼叫方看來,都是IHelloWorld介面, 並不會意識到其實底層其實已經滄海滄田了。”
“朕能明白你這個綠色的HelloWorld代理,但是你這個類怎麼可能知道把Logger的方法加到什麼地方呢?” 國王一下子看出了關鍵。
“陛下天資聰慧,臣拜服,‘刁民’們需要寫一個類來告訴我們具體把Logger的程式碼加到什麼地方, 這個類必須實現帝國定義的InvocationHandler介面,該介面中有個叫做invoke的方法就是他們寫擴充套件程式碼的地方。 比如這個LoggerHandler: ”
“ 看起來有些讓朕不舒服,不過朕大概明白了, 無非就是在呼叫真正的方法之前先呼叫Logger.startLog(), 在呼叫之後在呼叫Logger.end(), 這就是對方法進行攔截了,對不對?”
“正是如此! 其實這個LoggerHandler 充當了一箇中間層, 我們自動化生成的類$HelloWorld100會呼叫它,把sayHello這樣的方法呼叫傳遞給他 (上圖中的method變數),於是sayHello()方法就被新增上了Logger的startLog()和endLog()方法”
“此外,臣想提醒陛下的是,這個Handler不僅僅能作用於IHelloWorld 這個介面和 HelloWorld這個類,陛下請看,那個target 是個Object, 這就意味著任何類的例項都可以, 當然我們會要求這些類必須得實現介面。 臣民們使用LoggerHandler的時候是這樣的:”
輸出:
Start Logging
Hello World
End Logging
“如果想對另外一個介面ICalculator和類Calcualtor做代理, 也可以複用這個LoggerHandler的類:”
“折騰了變天,原來魔法是在Proxy.newProxyInstance(....) 這裡,就是動態地生成了一個類嘛, 這個類對臣民們來說是動態生成的, 也是看不到原始碼的。”
“聖明無過陛下,我就是在執行時,在記憶體中生成了一個新的類,這個類在呼叫sayHello() 或者add()方法的時候, 其實呼叫的是LoggerHanlder的invoke 方法, 而那個invoke就會攔截真正的方法呼叫,新增日誌功能了! ”
“愛卿辛苦了,雖然有點繞,但是理解了還是挺簡單的。 朕明天就頒發聖旨, 全國推行,對了你打算叫它什麼名字? ”
“既然是在執行時動態的生成類,並且作為一個真實物件的代理來做事情, 那就叫動態代理吧!”
動態代理技術釋出了,臣民們得到了暫時的安撫,但是這個動態代理的缺陷就是必須有接口才能工作,帝國的臣民能忍受得了嗎?