MOP——方法注入

前面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)方式:

  1. 分類(Category)
  2. ExpandoMetaClass
  3. Minxin
  4. 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):

  1. 其內(nèi)部定義的方法必須為 static
  2. 方法的第一個(gè)參數(shù)必須定位為目標(biāo)類的類型(eg:這里定義的 String,當(dāng)然你可以不寫類型帚屉,這樣就有可能讓多個(gè)類都是用該方法了),如果該方法還需要參數(shù)谜诫,那么就從第二個(gè)形參開始聲明。
  3. 第一個(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)造器
  • 屬性

接下來一一介紹如何注入:

  1. 非靜態(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)雅逆害。

  1. 靜態(tài)方法注入
    需要使用 'static' 的特殊字面量注入靜態(tài)方法
Foo.metaClass.'static'.bar = {}
Foo.bar()
  1. 注入構(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)部使用反射

  1. 注入屬性
    類似以閉包的方式注入方法翼抠,屬性注入也是支持的咙轩,只要在后面 = 具體值 即可。
Foo.metaClass.bar = 1
println foo.bar
  1. 一次注入多個(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媒惕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市来庭,隨后出現(xiàn)的幾起案子妒蔚,更是在濱河造成了極大的恐慌,老刑警劉巖月弛,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肴盏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡帽衙,警方通過查閱死者的電腦和手機(jī)菜皂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佛寿,“玉大人幌墓,你說我怎么就攤上這事〖叫海” “怎么了常侣?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)弹渔。 經(jīng)常有香客問我胳施,道長(zhǎng),這世上最難降的妖魔是什么肢专? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任舞肆,我火速辦了婚禮,結(jié)果婚禮上博杖,老公的妹妹穿的比我還像新娘椿胯。我一直安慰自己,他們只是感情好剃根,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布哩盲。 她就那樣靜靜地躺著,像睡著了一般狈醉。 火紅的嫁衣襯著肌膚如雪廉油。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天苗傅,我揣著相機(jī)與錄音抒线,去河邊找鬼。 笑死渣慕,一個(gè)胖子當(dāng)著我的面吹牛嘶炭,可吹牛的內(nèi)容都是我干的抱慌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼旱物,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼遥缕!你這毒婦竟也來了卫袒?” 一聲冷哼從身側(cè)響起宵呛,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夕凝,沒想到半個(gè)月后宝穗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡码秉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年逮矛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片转砖。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡须鼎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出府蔗,到底是詐尸還是另有隱情晋控,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布姓赤,位于F島的核電站赡译,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏不铆。R本人自食惡果不足惜蝌焚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望誓斥。 院中可真熱鬧只洒,春花似錦蚓庭、人聲如沸锉桑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)泡垃。三九已至析珊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蔑穴,已是汗流浹背忠寻。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留存和,地道東北人奕剃。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓衷旅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親纵朋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子柿顶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)操软,斷路器嘁锯,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,815評(píng)論 6 342
  • Groovy學(xué)習(xí)目錄-傳送門 元編程(Metaprogramming)->百度百科 Groovy語(yǔ)言支持兩種類型的...
    化作春泥_閱讀 9,057評(píng)論 0 19
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法聂薪,內(nèi)部類的語(yǔ)法家乘,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法藏澳,線程的語(yǔ)...
    子非魚_t_閱讀 31,632評(píng)論 18 399
  • 從離開學(xué)校到現(xiàn)在翔悠,算算也有一年的時(shí)間了业崖,這一年,每天深夜都會(huì)在心里默默許諾:從明天起蓄愁,要做這個(gè)双炕,要做那個(gè)........
    葉下柳蘇閱讀 463評(píng)論 0 0