1. 程式人生 > >動態代理之實現(父子)

動態代理之實現(父子)

1、深夜奏對

已經快三更天了, Java帝國的國王還在看著IO大臣的奏章發呆,他有點想不明白, 帝國已經給臣民了提供了這麼多的東西,他們為什麼還不滿意呢? 集合、IO、反射、網路、執行緒、泛型、JDBC ......在IT界哪一個不都是響噹噹的硬通貨?  有了這些技術,寫個Java程式多簡單啊, 臣民們為何還整天抗議呢?

 

這還是昨天IO大臣的一個奏章,其中說到各個部落要醞釀一場大規模的抗議遊行,抗議Java不支援動態性,不能在執行時修改一個類,導致不能用宣告的方式來程式設計。

 

國王憤憤地想,我的政策太開明瞭,這些刁民不知好歹,蹬鼻子上臉,以後要堅決加強東廠西廠錦衣衛鎮撫司等紀檢法的建設,有意見可以上訪,

不能這麼胡鬧,增加社會不穩定因素。帝國正在和Python, PHP等國家開戰,處處都要銀子,攘外必先安內啊。

 

想到這裡,國王立刻命令呂公公宣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就會攔截真正的方法呼叫,新增日誌功能了! ”

 

“愛卿辛苦了,雖然有點繞,但是理解了還是挺簡單的。 朕明天就頒發聖旨, 全國推行,對了你打算叫它什麼名字? ”

 

“既然是在執行時動態的生成類,並且作為一個真實物件的代理來做事情, 那就叫動態代理吧!

 

動態代理技術釋出了,臣民們得到了暫時的安撫,但是這個動態代理的缺陷就是必須有接口才能工作,帝國的臣民能忍受得了嗎?