Groovy基礎——MetaClass詳解

來源:互聯網
上載者:User

標籤:

一、攔截方法調用和參數擷取

樣本1:

class MyClass{ def hello(){  ‘invoked hello directly‘ } def invokeMethod(String name, Object args){  return "unknown method $name(${args.join(‘, ‘)})" }}def mine= new MyClass()assert mine.hello() == ‘invoked hello directly‘assert mine.foo("Mark", 19) == ‘unknown method foo(Mark, 19)‘

首先我們在groovy指令碼中定義了一個Myclass對象,在groovy中任何的對象都是實現GroovyObject並且繼承GroovyObjectSupport的,在GroovyObject的介面中,我們可以看到幾個方法首先是getMetaClass方法和setMetaClass方法,metaClass用來支援動態方法和動態參數的調用。另一組方法是getProperty和setProperty方法,這組方法是用來支援動態參數的設定與賦值的。最後還有一個invokeMethod方法,該方法則是用於調用動態方法的。在瞭解了上述概念後,我們可以理解為MyClass的invokeMethod方法覆蓋了GroovyObjectSupport中對應的方法,所以調用未預先定義的foo方法就會進入invokeMethod的實現。而hello方法預先定義則照舊。

樣本2:

class MyClass implements GroovyInterceptable{def hello(){‘invoked hello() directly‘}def invokeMethod(String name, Object args){"invoked method $name(${args.join(‘, ‘)})"}}def mine = new MyClass()assert mine.hello() == ‘invoked method hello()‘assert mine.foo(‘Mark‘,19) == ‘invoked method foo(Mark, 19)‘assert mine.&hello() == ‘invoked hello() directly‘

該例子和樣本1的不同在於實現了一個GroovyInterceptable介面,仔細看下這個介面的描述,可知道實現該介面的類被呼叫者法時都是預設使用invokeMethod方法,而不管該方法時動態產生或時靜態產生;第2點不同是如果要再此情況下調用原有定義號的方法,需要變通的使用‘.&‘操作符。

 

樣本3:

class MyClass{def greeting = ‘accessed greeting directly‘Object getProperty(String property){"read from property $property"}void setProperty(String property, Object newVlaue){throw new Exception("wrote to property $property")}}def mine = new MyClass()assert mine.greeting == ‘read from property greeting‘try{mine.greeting = ‘hi‘}catch(e){assert e.message == ‘wrote to property greeting‘}assert [email protected] == ‘accessed greeting directly‘

該樣本描述了屬性的擷取特性,在樣本1中已經描述設定和擷取屬性的方法時繼承來的,這裡不做贅述。預設的通過Gpath(見Gpath具體概念)來處理屬性的值,都是通過調用getProperty和SetProperty來代勞的。同樣的如果你真希望直接存取參數的值,可以變通的使用‘[email protected]‘操作符來達成。

通過Gpath來獲得屬性值。無論該屬性在類中是否有,都是不會出錯的執行那2個方法。但是對於類中不存在的屬性,忌使用‘[email protected]‘操作符,會拋出MissingFieldException。

 

樣本4:

class MyClass implements GroovyInterceptable{def greeting = ‘accessed greeting‘def id =‘White: ‘Object getProperty(String property){try{return [email protected] + //access field directly‘indirectly ‘ +[email protected]"$property"}catch(e){return "no such property $property"}}def hello(Object[] args){"invoked hello with (${args.join(‘, ‘)})"}def id(){‘Green: ‘}def invokeMethod(String name, Object args){try{return this.&id() + //call method directly‘indirectly ‘ +this.&"$name"(args)}catch(e){return "no such method $name"}}}def mine = new MyClass()assert mine.greeting == ‘White: indirectly accessed greeting‘assert mine.farewell == ‘no such property farewell‘assert mine.hello(1, ‘b‘, 3) == ‘Green: indirectly invoked hello with (1, b, 3)‘assert mine.foo(‘Mark‘, 19) == ‘no such method foo‘

該樣本是對樣本 2,3的一個合并,同時他告訴我們我們可以通過操作符‘[email protected]‘ 或者‘.&‘後使用雙引號中定義變數的方法來動態擷取參數或者動態方法。

二、MetaClass (描述類的類)

 

樣本5:

public class MyMetaClass extends DelegatingMetaClass{MyMetaClass(Class thisClass){super(thisClass)}Object invokeMethod(Object object, String methodName, Object[] arguments){"MyMetaClass: ${super.invokeMethod(object, methodName, arguments)}"}}class A{def bark(){‘A: invoked bark()‘}def invokeMethod(String name, Object args){"A: missing $name(${args.join(‘, ‘)})"}}def amc = new MyMetaClass(A)amc.initialize()def a = new A()a.metaClass = amcassert a.bark() == ‘MyMetaClass: A: invoked bark()‘Thread.start {assert a.bark() == ‘MyMetaClass: A: invoked bark()‘}assert new A().bark() == ‘A: invoked bark()‘assert a.bleet() == ‘A: missing bleet()‘
該樣本的代碼較長,主要的意思是我們可以通過任意Groovy對象中的metaClass屬性來為改變該對象的方法調用的行為。Groovy為我們提供了 DelegatingMetaClass 來讓我們實現該功能。

具體的做法是:首先建立一個自訂的MetaClass類繼承於DelegatingMetaClass,同時實現構造方法,以及自訂的方法調用行為(以後的內容將介紹,我們不僅可以改變方法行為)。然後我們可以通過自己建立的metaClass子類來封裝你想改變行為的目標類。此處為類A。然後再建立目標類執行個體,對目標類中得metaClass屬性進行設定。隨後在該執行個體上的方法調用將會按照該執行個體的metaClass屬性所對應的處理器,進行處理,該例中是再完成本身方法調用後在前面添加MyMetaClass字串。

但是值得注意的是,1.對於另外新建立的A執行個體,如果沒有進行metaClass屬性賦值將按照原方法定義執行;2.動態定義的

方法不受metaClass方法行為改變的影響。見該樣本的最後一行。3.上述的特性在新線程內同樣有效。

 

樣本6:

InvokerHelper.instance.metaRegistry.setMetaClass(A, amc)

該樣本只是對樣本5的補充,我們曾提到,我們可以通過為目標類執行個體設定metaClass屬性來讓metaClass的行為對該執行個體生效。但是如果要在類範圍內生效的話,就需要通過上面的代碼進行註冊。這樣註冊過後,將對目標類的所有,任何時候建立的樣本,都賦予metaClass的行為。

這個樣本還有一點需要注意。試想這麼一種情況,先建立了一個目標類執行個體,再用樣本6的語句註冊,再建立一個目標類的執行個體。metaClass的行為將在哪個對象上生效呢。答案是兩者都生效。這點非常關鍵。一旦進行了類範圍metaClass註冊,那對於已建立和新建立的對象都生效。

註:對於高版本的groovy InvokerHelper的instance不存在。可以直接調用metaRegistry。

 

樣本7:

import org.codehaus.groovy.runtime.InvokerHelper;public class MyMetaClass extends DelegatingMetaClass{MyMetaClass(Class theClass){super(theClass)}Object invokeConstructor(Object[] arguments){[]}}class A{}def amc = new MyMetaClass(A)amc.initialize()InvokerHelper.metaRegistry.setMetaClass(A,amc)def a = new A()assert a.class == ArrayListassert (a<<1<<2<<3).size() == 3

在之前的樣本已經略有提過metaClass不僅可以改變預定義的方法行為。在該樣本中就以改變構造行為為例。該metaClass將構造行為變成建立一個數組對象。隨後的操作是一目瞭然的。

在此值得一提的是,invokeConstructor方法時從metaClass的介面MetaObjectProtocol繼承過來,其他的方法可能

是從不同的繼承樹而來的,所以可以參見DelegatingMetaClass中得方法簽名。我們可以發現有很多invokeXXX和getXXX的方法,也就是說我們可以對這些metaClass行為做更改。主要的我們可以看到有getProperty方法invokeConstructor方法,當然我很興奮的發現還有一個invokeMissingMethod方法,這個方法似乎就是對我們樣本5中不能處理動態定義方法的metaClass行為的一個有用的補充了。

 三、ExpandoMetaClass

下面這個樣本內容較多,在給出例子之前,需要先澄清一些細節,你可能會想我們之前人為的為groovyobject設定metaClass來改變類執行個體的行為,那預設不設定情況下這個metaClass是什麼呢? 其實無論是你設定或者沒有設定metaClass它的metaClass都是HandleMetaClass類型的,但是區別在於,裡面有一個getAdaptee方法返回的是DelegatingMetaClass中的MetaClass類型的delegate屬性,預設情況下,這個delegate會事MetaClassImpl類型的,但是一旦你自己設定了目標類執行個體的metaClass屬性那這個delegate屬性就是你設定的了。我們下面要將的這個話題,也就是這一章的標題ExpandoMetaClass(MetaClassImpl的一個子類)正是又一個與MetaClassImpl以及自訂的MetaClass平行的delegate只是這個delegate,支援動態建立方法等等的特性。接著我們先引入樣本

樣本8:

class A{String text  }def a1= new A(text: ‘aBCdefG‘)assert a1.metaClass.adaptee.class == MetaClassImplA.metaClass.inSameCase = {-> text.toUpperCase()}//triggers conversion of MetaClass of A to ExpandoMetaClass//then adds new instance method ‘inUpperCase‘ to class//A.metaClass {  }//def a2 = new A(text:‘hiJKLmnOp‘)assert a2.metaClass.adaptee.class == ExpandoMetaClass//MetaClass of A changed for instances created after conversion trigger onlyassert a2.inSameCase() == ‘HIJKLMNOP‘//new method not availableassert a1.metaClass.adaptee.class == MetaClassImpltry{ println a1.inSameCase();}catch(e){assert e in MissingMethodException}A.metaClass.inLowerCase = {-> text.toLowerCase()}assert a2.inLowerCase() == ‘hijklmnop‘//replace the method definition with anotherA.metaClass.inSameCase = {-> text.toLowerCase()}assert a2.inSameCase() == ‘hijklmnop‘//add static methodsA.metaClass.‘static‘.inSameCase = {it.toLowerCase()}assert A.inSameCase(‘qRStuVwXyz‘) == ‘qrstuvwxyz‘
 代碼的前幾行印證了,預設的delegate(即HandleMetaClass中得metaClass屬性)是MetaClassImpl。然後我們調用了 A.metaClass {  }方法(該方法位於DefaultGroovyMethods中)使得返回的HandleMetaClass中得delegate是 

ExpandoMetaClass類型的。這樣我們就能夠利用該類型的metaClass為我們做事了。同樣的

A.metaClass.inSameCase = {-> text.toUpperCase()}方法只是在註冊metaClass為ExpandoMetaClass的同時

為其動態添加一個執行個體方法。同時我們可以在最後幾行的代碼中獲知,動態添加靜態方法也是可行的。

幾點需要注意的:1.只有在切換delegate為ExpandoMetaClass後建立的目標對象才能支援切換時所提供的動態方法。2.動態方法的添加存在覆蓋關係。

樣本9:

 

class A{}A.metaClass.character = ‘Cat in the Hat‘def a1 = new A()assert a1.character == ‘Cat in the Hat‘def ourProperties = Collections.synchronizedMap([:])A.metaClass.setType = {String value -> ourProperties["${delegate}Type"] = value }A.metaClass.getType = { -> ourProperties["${delegate}Type"]}a1.type = ‘Hatted Cat‘assert a1.type == ‘Hatted Cat‘def a2 = new A()A.metaClass.constructor = { -> new A()}try{a2 = new A()}catch(Error e){assert e in StackOverflowError}A.metaClass.constructor = {String s -> new A(character :s)}a2 = new A("Thing One")A.metaClass.‘changeCharacterToThingTwo‘= {-> delegate.character = ‘Thing Two‘ }a2.character= ‘Cat in the Hat‘a2.changeCharacterToThingTwo()assert a2.character == ‘Thing Two‘[‘Hatted Cat‘, ‘Thing‘, ‘Boy‘, ‘Girl‘, ‘Mother‘].each{p->  A.metaClass."changeTypeTo${p}"= {-> delegate.type= p}}a2.changeTypeToBoy()assert a2.type == ‘Boy‘a2.‘changeTypeToHatted Cat‘()assert a2.type == ‘Hatted Cat‘

 

該樣本的內容只要是樣本8的一個補充,我們不僅可以動態添加方法,同時還可以動態添加屬性和構造方法。

樣本10:


ExpandoMetaClass.enableGlobally()//call ‘enableGlobally‘ method before adding to supplied classList.metaClass.sizeDoubled = {-> delegate.size() * 2 }//add method to an interfacedef list = [] << 1 << 2assert list.sizeDoubled() == 4
該樣本比較簡介,旨在告訴我們我們不僅可以對自訂的groovy對象進行屬性方法等的動態添加,同樣的我們可以對非自訂的Groovy提供的對象進行動態處理。處理方法和自訂對象的方法時完全一樣的,唯一的區別在於,我們需要額外定義 ExpandoMetaClass.enableGlobally(),然而筆者發現筆者使用的1.8.1版的groovy如果去掉了該聲明也是可以工作

所以請各位讀者按照個人版本做嘗試。

 

樣本11:

class Bird{def name = ‘Tweety‘def twirp(){ ‘i taught i saw a puddy cat‘ }}Bird.metaClass.invokeMethod = {name, args->def metaMethod = Bird.metaClass.getMetaMethod(name, args)metaMethod?metaMethod.invoke(delegate,args): ‘no such method‘}def a = new Bird()assert a.twirp() == ‘i taught i saw a puddy cat‘assert a.bleet() ==‘no such method‘Bird.metaClass.getProperty = {name->def metaProperty = Bird.metaClass.getMetaProperty(name)metaProperty?metaProperty.getProperty(delegate): ‘no such property‘}def b = new Bird()assert b.name == ‘Tweety‘assert b.filling == ‘no such property‘

 該樣本主要說明的是我們不僅可以用Expando特性來添加方法屬性和構造方法,同樣的我們可以對已經存在的方法進行覆蓋。最後還要強調一下Bird.metaClass返回的是ExpandoMetaClass我們這裡覆蓋的getMetaMethod和

getProperty以及invokeMethod和getProperty方法都是對ExpandoMetaClass類和它父類對應的方法進行覆蓋。

至此,Groovy的MetaClass內容已經介紹完了。該部分內容在groovy中非常重要。謹此記錄下來作為日後參考,同時希望對大家有協助。

 

 

 



 

 

 

 

Groovy基礎——MetaClass詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.