前面MOP——方法攔截介紹了利用 MOP 對(duì)方法的調(diào)用進(jìn)行攔截闹击,接下來要介紹利用 MOP 實(shí)現(xiàn)方法的注入蛾魄。
方法攔截和方法注入的區(qū)別
攔截:側(cè)重對(duì)于已有的方法的調(diào)用進(jìn)行攔截
注入:對(duì)一個(gè)已有的類添加新方法,以拓展該類的功能。
eg:Java 中提供了 String 的類榨汤,如果我們想擴(kuò)展該類军掂,為其提供一個(gè)字符串加密方法轮蜕。在 Java 中,最常見的做法是提供一個(gè)接口蝗锥,內(nèi)有 encrypt() 方法跃洛,讓目標(biāo)類實(shí)現(xiàn)該接口;或者繼承該類终议,然后添加一個(gè) encrypt() 方法汇竭。但是這里存在的問題是:我們未必可以修改想要擴(kuò)展的類,就像 String穴张,還是 final 的细燎,只能用,不能改皂甘。
不過使用 Groovy玻驻,就可以方便的為任何類擴(kuò)展方法,同時(shí)在使用起來偿枕,給人的感覺就好像注入的類是該類本身就有的璧瞬。
MOP 的注入有四種實(shí)現(xiàn)方式:
- 分類(Category)
- ExpandoMetaClass
- Minxin
- trait
使用分類進(jìn)行方法注入
第一次接觸 Category 的概念是在學(xué)習(xí) Objective-C 的時(shí)候,只要自定義一個(gè)和目標(biāo)類相同的類渐夸,然后在自定義類中添加方法嗤锉,那么在方法調(diào)用時(shí),會(huì)先從自定義的類中查找墓塌,找不到后再去原本的類中查找瘟忱。這樣一來不僅可以擴(kuò)展類的方法,同時(shí)還可以覆蓋原有類的方法苫幢。Objective-C 中 Category 感覺是最優(yōu)雅的方式了访诱。而 Groovy 中的 Category 就遜色的多,接下來看一下 Groovy 中 Category 的使用方法态坦,這里以向 String 中添加一個(gè) encrypt 方法為例盐数。
class StringUtils {
def static encrypt(String self) {
byte[] arr = self.bytes;
for (int i = 0; i < arr.length; i++) {
arr[i] = (127 - arr[i])
}
return new String(arr)
}
}
class IntegerUtils{
def static add(Integer a, int b) {
a + b
}
}
use(StringUtils,IntegerUtils) {
String str = "hello"
s = str.encrypt()
println 1.add(2)
}
這里先定義了一個(gè)的類,其中定義了一個(gè)方法 encrypt(),要想要該類成為 String 的分類伞梯,需要注意以下幾點(diǎn):
- 其內(nèi)部定義的方法必須為
static
- 方法的第一個(gè)參數(shù)必須定位為目標(biāo)類的類型(eg:這里定義的 String,當(dāng)然你可以不寫類型帚屉,這樣就有可能讓多個(gè)類都是用該方法了),如果該方法還需要參數(shù)谜诫,那么就從第二個(gè)形參開始聲明。
- 第一個(gè)參數(shù)如果聲明類型攻旦,必須為包裝類的類型喻旷,eg:如果我們想為整數(shù)提供方法,即使用到了
1.add(2)
這樣的調(diào)用方式牢屋,但這是 groovy 提供的語(yǔ)法糖且预,其本質(zhì)任為 Integer槽袄,因此在定義分類的方法時(shí),第一個(gè)參數(shù)必須是包裝類型
在使用時(shí)锋谐,其必須在 use 所定義的代碼塊中遍尺,出了代碼塊就無(wú)法使用分類中的方法了,否則報(bào)找不到方法的錯(cuò)誤涮拗。在 use 后面必須注明要注入的方法所在的類乾戏,eg:use(StringUtils),use 中可以注入多個(gè)類三热,如果多個(gè)分類中有重復(fù)的方法定義鼓择,那么以最后一個(gè)分類中方法為準(zhǔn)。
使用 ExpandoMetaClass 進(jìn)行方法注入
注入概述
之前其實(shí)我們已經(jīng)見到過使用 ExpandoMetaClass 注入方法的示例了就漾,就是使用MetaClass 進(jìn)行方法攔截呐能,這本質(zhì)就是方法的注入,只不過注入的方法名(invokeMethod)比較特殊抑堡,成為了方法攔截催跪。同樣,我們也可以用 ExpandoMetaClass 對(duì)類進(jìn)行其它方法的注入夷野,還拿上面 Integer 的加法的例子:
Integer.metaClass.add = {
int i ->
delegate + i
}
println 1.add(3)
注入的種類
使用 ExpandoMetaClass 方法注入懊蒸,可以對(duì)以下三種方法進(jìn)行注入:
- 非靜態(tài)方法
- 靜態(tài)方法
- 構(gòu)造器
- 屬性
接下來一一介紹如何注入:
- 非靜態(tài)方法注入
這在前面已經(jīng)見到過了,也是最常用的注入悯搔,使用方法:
Foo.metaClass.bar = {}
foo.bar()
Groovy 的設(shè)計(jì)理念就是讓程序的編寫更加流程骑丸,因此在 DSL 中,可能更常見的一種形式是在調(diào)用方法時(shí)不寫括號(hào)妒貌,即foo.bar
但是沒有括號(hào)調(diào)用時(shí)通危,會(huì)將方法的調(diào)用當(dāng)成屬性,所以需要對(duì)之前的注入進(jìn)行修改灌曙。
Foo.metaClass.getBar = {}
foo.bar
這樣的調(diào)用方式是否更加優(yōu)雅呢菊碟,在后面的 DSL 中,還會(huì)進(jìn)一步講解 groovy 的語(yǔ)法糖在刺,讓編程更加優(yōu)雅逆害。
- 靜態(tài)方法注入
需要使用'static'
的特殊字面量注入靜態(tài)方法
Foo.metaClass.'static'.bar = {}
Foo.bar()
- 注入構(gòu)造器
使用constructor
屬性注入構(gòu)造器
添加一個(gè)構(gòu)造器 <<
替換一個(gè)構(gòu)造器 =
Foo.metaClass.constructor << {
int i ->
Foo foo = new Foo();
foo.i = i
foo
}
構(gòu)造方法注入特別要注意的是,要確保沒有遞歸調(diào)用自身蚣驼,否則棧溢出魄幕。因?yàn)槲覀兪窍攵x構(gòu)造器,肯定會(huì)借助現(xiàn)有的構(gòu)造器颖杏,然后進(jìn)行屬性的改造纯陨,但是不要產(chǎn)生遞歸。如果是想覆蓋構(gòu)造器的話,那么只能在內(nèi)部使用反射
- 注入屬性
類似以閉包的方式注入方法翼抠,屬性注入也是支持的咙轩,只要在后面 = 具體值 即可。
Foo.metaClass.bar = 1
println foo.bar
- 一次注入多個(gè)方法
Groovy 提供了使用ClassName.metaClass.method = { ... }
這樣的語(yǔ)法向 metaClass 中添加阴颖,既簡(jiǎn)單又方便活喊,但如果想添加一堆方法,這樣的聲明就會(huì)感覺很費(fèi)勁膘盖。groovy 提供了更簡(jiǎn)潔的語(yǔ)法胧弛,用來減少噪音!侠畔!這種方式也是在 DSL 中常見到的结缚。
Foo.metaClass = {
bar1 = {}
bar2 = {}
'static'{
bar3 = {}
}
//針對(duì)于不管是覆蓋還是注入,在這種語(yǔ)法環(huán)境下软棺,都應(yīng)該使用 =
constructor = {
int i - >
}
constructor = {
int i,int j ->
}
}
再次重申:使用 ExpandoMetaClass 注入的閉包中红竭,delegate 指的是調(diào)用該方法的對(duì)象,在此基礎(chǔ)上喘落,閉包中使用類原本的成員變量茵宪,或者方法也是可以的。
向單個(gè)實(shí)例中注入方法
前面介紹的是向整個(gè)類中注入方法瘦棋,那么基于該類的所有對(duì)象都可以使用閉包中的方法稀火。如果只是想擴(kuò)展該類的某一個(gè)對(duì)象的方法,而不影響該類的其它對(duì)象赌朋,該如何處理呢凰狞?
其實(shí)不單是 Class,每個(gè)具體的對(duì)象也包含一個(gè) metaClass沛慢,我們可將創(chuàng)建一個(gè)具體的 ExpandoMetaClass 實(shí)例赡若,并將制定方法加入其中,然后將其賦給對(duì)應(yīng)的具體對(duì)象团甲,也可以將方法直接注入到具體的對(duì)象的 metaClass 上逾冬。
//方式 1
class Man{
def talk(){}
}
def emc = new ExpandoMetaClass(Man)
emc.sing = { -> ... }
emc.initialize()
def mike = new Man()
mile.metaClass = emc
mike.sing()
//方式 2
mike.metaClass.dance = { -> ...}
mike.dance()
//卸載之前注入的方法
mike.metaClass = null
很明顯方式 2 是最為優(yōu)雅的,因此推薦使用方式 2躺苦。同時(shí)身腻,當(dāng)我們?yōu)橐粋€(gè)對(duì)象注入了方法,在使用了一段時(shí)間不想使用后圾另,那么很方便的卸載之前注入的方法霸株。
ExpandoMetaClass 小結(jié)
使用 ExpandoMetaClass,無(wú)論是注入方法集乔,還是調(diào)用方法,都比 Category 要優(yōu)雅的多。因此推薦使用該方法扰路。
但是要注意的是尤溜,如果對(duì)象想使用注入的方法,必須要先進(jìn)行注入汗唱。如果在已經(jīng)有對(duì)象產(chǎn)生之后再向類中注入方法宫莱,那么該對(duì)象無(wú)法調(diào)用注入的方法!哩罪!
因此使用 ExpandoMetaClass 進(jìn)行注入授霸,最好是在整個(gè)應(yīng)用初始化時(shí)進(jìn)行。
同時(shí)方法注入具有繼承性际插。如果向 Object 注入了方法碘耳,那么所有的類都可以使用該方法。
使用 Minxin框弛,trait 進(jìn)行方法注入
這兩種方式更像是開頭提到的定義接口的實(shí)現(xiàn)方式辛辨。
個(gè)人感覺最為強(qiáng)大的方式是 Mixin 的方式∩悖可以為類注入多個(gè) Mixin斗搞,就好想讓類實(shí)現(xiàn)了多個(gè)接口,同時(shí)接口中相同的方法慷妙,以后面加入的為準(zhǔn)僻焚。
這里不再重點(diǎn)展開了。
Groovy Mixin 注入
Groovy 2.3 introduces traits
Mixins and traits
實(shí)現(xiàn)方式的優(yōu)劣對(duì)比
-
Category 存在的問題:其作用被限定在 use()塊內(nèi)膝擂,所以也就限定于當(dāng)前執(zhí)行的線程虑啤。進(jìn)入該 use()塊內(nèi)的代碼會(huì)在當(dāng)前線程創(chuàng)建一個(gè)棧幀,并壓入到當(dāng)前線程的棧上猿挚,而當(dāng) use 代碼塊結(jié)束后咐旧,當(dāng)前線程的棧會(huì)將剛剛壓入的棧幀彈棧。但是如果頻繁的調(diào)用 use 代碼塊绩蜻,勢(shì)必會(huì)對(duì)性能造成一定的影響铣墨。
凡事都有兩面性,Categoty 的使用 use 塊办绝,提供了更好的隔離性伊约,我們可以再不同的地方,使用不同的分類孕蝉,這也為類的擴(kuò)展提供了靈活性屡律。
trait:缺點(diǎn)是在有類的修改權(quán)的情況下才能使用,類似接口降淮。
Mixin:其實(shí)是最強(qiáng)大的方式超埋,但需要對(duì)其有進(jìn)一步的了解,以防走火。霍殴。
這里推薦使用 ExpandoMetaClass媒惕。