这篇文章将对Groovy的其中一个核心内容MetaClass(MOP)讲解。由于该部分内容较多。所以还是利用一个个例子逐步的阐述。
?
一、拦截方法调用和参数获取
示例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{??
????????'invoked?hello()?directly'??
????????"invoked?method?$name(${args.join(',250)"> ??
def?mine?=?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:
????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")??
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来代劳的。同样的如果你真希望直接访问参数的值,可以变通的使用'.@'操作符来达成。
通过Gpath来获得属性值。无论该属性在类中是否有,都是不会出错的执行那2个方法。但是对于类中不存在的属性,忌使用'.@'操作符,会抛出MissingFieldException。
示例4:
????def?greeting?=?'accessed?greeting'??
????def?id?='White:?'??
??????
????????????return?this.@id?+???
????????????????????'indirectly?'?+??
????????????????????this.@"$property"??
????????}return?"no?such?property?$property"??
????????}??
????def?hello(Object[]?args){"invoked?hello?with?(${args.join(',?')})"}??
????def?id(){'Green:?'}??
this.&id()?+???
this.&"$name"(args)??
return?"no?such?method?$name"??
????}?????
assert?mine.greeting?==?'White:?indirectly?accessed?greeting'??
assert?mine.farewell?==?'no?such?property?farewell'??
assert?mine.hello(1,?'b',0)">3)?==?'Green:?indirectly?invoked?hello?with?(1,?b,?3)'??
19)?==?'no?such?method?foo'??
该示例是对示例 2,3的一个合并,同时他告诉我们我们可以通过操作符'.@' 或者'.&'后使用双引号中定义变量的方法来动态的获取参数或者动态的方法。
二、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()'}??
????????"A:?missing?$name(${args.join(',250)"> def?amc?=?new?MyMetaClass(A)??
amc.initialize()??
def?a?=?new?A()??
a.metaClass?=?amc??
assert?a.bark()?==?'MyMetaClass:?A:?invoked?bark()'??
Thread.start?{??
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;??
????MyMetaClass(Class?theClass){??
super(theClass)??
????Object?invokeConstructor(Object[]?arguments){??
????????[]??
class?A{}??
InvokerHelper.metaRegistry.setMetaClass(A,amc)??
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行为的一个有用的补充了。
三、ExpandoMetaClass
下面这个示例内容较多,在给出例子之前,需要先澄清一些细节,你可能会想我们之前人为的为groovyobject设置metaClass来改变类实例的行为,那默认不设置情况下这个metaClass是什么呢? 其实无论是你设置或者没有设置metaClass它的metaClass都是HandleMetaClass类型的,但是区别在于,里面有一个getAdaptee方法返回的是DelegatingMetaClass中的MetaClass类型的delegate属性,默认情况下,这个delegate会事MetaClassImpl类型的,但是一旦你自己设置了目标类实例的metaClass属性那这个delegate属性就是你设置的了。我们下面要将的这个话题,也就是这一章的标题ExpandoMetaClass(MetaClassImpl的一个子类)正是又一个与MetaClassImpl以及自定义的MetaClass平行的delegate只是这个delegate,支持动态创建方法等等的特性。接着我们先引入示例
示例8:
????String?text??
??}??
def?a1=?new?A(text:?'aBCdefG')??
assert?a1.metaClass.adaptee.class?==?MetaClassImpl??
A.metaClass.inSameCase?=?{->?text.toUpperCase()}??
??
//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??
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:
A.metaClass.character?=?'Cat?in?the?Hat'??
def?a1?=?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'??
A.metaClass.constructor?=?{?->?new?A()}??
????a2?=?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'??
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?=?assert?b.name?==?'Tweety'??
assert?b.filling?==?'no?such?property'??
?该示例主要说明的是我们不仅可以用Expando特性来添加方法属性和构造方法,同样的我们可以对已经存在的方法进行覆盖。最后还要强调一下Bird.metaClass返回的是ExpandoMetaClass我们这里覆盖的getMetaMethod和
getProperty以及invokeMethod和getProperty方法都是对ExpandoMetaClass类和它父类对应的方法进行覆盖。
至此,Groovy的MetaClass内容已经介绍完了。该部分内容在groovy中非常重要。谨此记录下来作为日后参考,同时希望对大家有帮助。 
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|