Groovy基礎——MetaClass詳解

1、攔截方法調用和參數獲取

示例1: java

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: this

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點不一樣是若是要再此狀況下調用原有定義號的方法,須要變通的使用'.&'操做符。 spa

 

示例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 mine.@greeting == 'accessed greeting directly'

該示例描述了屬性的獲取特性,在示例1中已經描述設置和獲取屬性的方法時繼承來的,這裏不作贅述。默認的經過Gpath(見Gpath具體概念)來處理屬性的值,都是經過調用getProperty和SetProperty來代勞的。一樣的若是你真但願直接訪問參數的值,能夠變通的使用'.@'操做符來達成。 code

經過Gpath來得到屬性值。不管該屬性在類中是否有,都是不會出錯的執行那2個方法。可是對於類中不存在的屬性,忌使用'.@'操做符,會拋出MissingFieldException。 對象

 

示例4: 繼承

class MyClass implements GroovyInterceptable{
	def greeting = 'accessed greeting'
	def id ='White: '
	
	Object getProperty(String property){
		try{
			return this.@id + //access field directly
					'indirectly ' +
					this.@"$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的一個合併,同時他告訴咱們咱們能夠經過操做符'.@' 或者'.&'後使用雙引號中定義變量的方法來動態的獲取參數或者動態的方法。 接口

2、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 = amc


assert 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 == ArrayList
assert (a<<1<<2<<3).size() == 3

在以前的示例已經略有提過metaClass不只能夠改變預約義的方法行爲。在該示例中就以改變構造行爲爲例。該metaClass將構造行爲變成建立一個數組對象。隨後的操做是一目瞭然的。

在此值得一提的是,invokeConstructor方法時從metaClass的接口MetaObjectProtocol繼承過來,其餘的方法可能

是從不一樣的繼承樹而來的,因此能夠參見DelegatingMetaClass中得方法簽名。咱們能夠發現有不少invokeXXX和getXXX的方法,也就是說咱們能夠對這些metaClass行爲作更改。主要的咱們能夠看到有getProperty方法invokeConstructor方法,固然我很興奮的發現還有一個invokeMissingMethod方法,這個方法彷佛就是對咱們示例5中不能處理動態定義方法的metaClass行爲的一個有用的補充了。

 3、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 == MetaClassImpl

A.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 only
assert a2.inSameCase() == 'HIJKLMNOP'

//new method not available
assert a1.metaClass.adaptee.class == MetaClassImpl
try{ println a1.inSameCase();}
catch(e){assert e in MissingMethodException}

A.metaClass.inLowerCase = {-> text.toLowerCase()}
assert a2.inLowerCase() == 'hijklmnop'

//replace the method definition with another
A.metaClass.inSameCase = {-> text.toLowerCase()}
assert a2.inSameCase() == 'hijklmnop'

//add static methods
A.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 class
List.metaClass.sizeDoubled = {-> delegate.size() * 2 }
//add method to an interface
def list = [] << 1 << 2
assert 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中很是重要。謹此記錄下來做爲往後參考,同時但願對你們有幫助。

相關文章
相關標籤/搜索