1. 程式人生 > >動態代理之繼承(兄弟)

動態代理之繼承(兄弟)

前言:本文為《Java帝國之動態代理》的姊妹篇, 講述動態代理的另外一種實現即CGLib的設計過程。

當IO大臣絞盡腦汁地在府中設計Java動態代理的時候,他並不知道,在帝國的一個小小的部落,一個年輕的小夥子正在為同樣的問題而苦惱。

師傅剛剛給小夥子下達了任務:在執行時對一個類進行擴充套件, 例如有個類叫HelloWorld,要在執行時給他加點日誌輸出的程式碼。
在這裡插入圖片描述

師傅特別告訴年輕人:“大胖, 你要注意,是在執行時,而不是在編譯時! 不能改變原來的程式碼!”
年輕氣盛的張大胖還不太清楚為什麼要這麼幹,不由地問道:“師傅,怎麼會有這麼稀奇古怪的要求啊?”
師傅說:“你先把這個東西給做出來,我以後自然會給你講。”
張大胖研究了一段時間後得知,Java帝國嚴禁在執行時對一個類進行修改,比如HelloWorld 這個類, 一旦被裝載進入JVM方法區,就不能改動了,那想在執行時給它的sayHello()方法中加點日誌程式碼該怎麼辦呢?

大胖心想: 那其實只剩下一條路了,動態地生成一個新類, 讓這個類作為代理HelloWorld去做事情。
在這裡插入圖片描述

可是怎麼才能動態地生成一個類呢? 張大胖心想: 我曾經看過帝國的《JVM規範》, 一個class檔案那簡直是太複雜了,難道讓我去操作二進位制程式碼去生成類? 那可要了我的小命了!

張大胖拿著草稿圖去找師傅:“師傅,我已經有了個初步想法, 可是不知道怎麼去動態地生成類啊!”

師傅看了一眼說: “你這個類圖有問題啊!”

“什麼問題?”

“舉個例子,有個函式如下

在這裡插入圖片描述

現在你把HelloWorld變成了HelloWorldProxy, 而這個HelloWorldProxy又和原來的HelloWorld一毛錢關係都沒有, 還能作為引數傳遞給hello方法嗎? ”
張大胖撓撓頭,挺不好意思 : “奧,那是肯定不行嘍,沒法利用多型,這樣,我可以讓這個動態生成的HelloWorldProxy繼承自HelloWorld, 師傅你看看這樣行不行:”
在這裡插入圖片描述

“這樣好多了, 但是還有一個問題,就是程式碼的複用性不夠。”

“什麼複用性?”

“你想想啊,現在你只有一個類就是HelloWorld , 如果還有很多別的類例如Person, Student, Employee,Teacher … 他們相應的方法都要加上日誌輸出,按照你的辦法,就得有無數這樣的程式碼了:
Logger.startLog();
super.XXXX();
Logger.endLog();”

“奧,我明白了,就是要想辦法複用這一段程式碼,那我可以再增加一箇中間層,就叫做LogInterceptor如何?”

在這裡插入圖片描述

“孺子可教,你這個名稱起得也不錯,Interceptor,意味著攔截的意思。這樣一來LogInterceptor就可以被其他類給複用了。”

“ 師傅, 回到我最初的問題, 怎麼在執行時動態地生成Java Class啊? 總不能讓我直接寫Java位元組碼吧?”

“這個你不用擔心,有個叫做ASM的傢伙,他已經對底層的Java位元組碼操作做了封裝,你直接呼叫它就行了”
“好, 讓我去看看,這個玩意兒到底怎麼樣。”
ASM確實挺難的, 雖然對位元組碼操作做了封裝,但是非得理解JVM指令才行,張大胖不得不去學習一下JVM位元組碼的知識,兩個月後,他終於能夠使用ASM動態的在記憶體中建立類了。

又花了兩個月,張大胖終於把整個系統開發完成,現在的使用非常簡單:

在這裡插入圖片描述

張大胖把這個東西命名為動態代理, 因為所做的所有事情無非就是在執行時為原有的類建立一個代理,增加功能而已。

過了兩天, 師傅急匆匆地來找張大胖:“大胖, 我剛剛聽說, Java帝國的IO大臣在JDK中加入了一個重要功能,叫做Java 動態代理,你趕緊研究下,看看和咱們做的有什麼不同。”

大胖不敢怠慢,趕緊檢視帝國發布的公告文書, 看完以後就放心了:“師傅, 這官方的動態代理有個重大的缺陷,就是必須有接口才能使用,而我們做的動態代理只要有個類就可以了, 我可以動態地生成一個子類。當然如果一個類被標記為final , 無法被繼承,那就不行了。”
在這裡插入圖片描述

“嗯,有點意思” 師傅說道 “這官方動態生成的HelloWorldProxy是HelloWorld的兄弟, 而我們動態生成的HelloWorldProxy是HelloWorld的孩子啊!”

“哈哈,果然是這樣。 師傅,你還沒給我說這玩意兒到底有啥用處呢”

“你沒看官方的公告嗎?”

“官方大話套話連篇,看不懂啊!”

“還拿之前的例子來說吧,你現在有很多類,例如Person, Student, Employee,Teacher … , 每個類都有很多方法, 現在你想給這些方法加上日誌輸出,該怎麼辦呢?”

張大胖說:“我可以去改動程式碼, 嗯,這樣改動量非常大,並且如果拿不到原始碼的話,就沒辦法了。”

“對啊,這時候動態代理不就派上用場了? 動態生成代理類PersonProxy, StudentProxy,EmployProxy… 等等, 讓它們去繼承Person, Student, Employee, 這樣代理類就可以增加日誌輸出程式碼了。 你甚至可以把要新增日誌功能的類和方法寫到一個XML檔案中去, 然後再寫個工具去讀取這個XML檔案,自動地生成所有代理類,多方便啊。”

“奧,原來如此 ,不僅僅是日誌,還有事務了, 許可權檢查了,都可以用這種辦法,對吧 ” 張大胖一點就通。

“是這樣的, 這就是AOP程式設計了。 對了,既然官方已經把動態代理這個名稱給佔了, 我們就得改名了,不能叫做動態代理了”

“師傅,這個我已經想好了,叫做Code Generation Library, 簡稱CGLib, 體現了技術的本質,就是一個程式碼生成的工具。”

後記:CGLIb為了提高效能,還用了一種叫做FastClass的方式來直接呼叫一個物件的方法,而不是通過反射。 由於涉及的程式碼太多,本文不再展示,一個具體的呼叫過程參見下圖:

在這裡插入圖片描述

本文出自 碼農翻身