Groovy基础——MetaClass详解

这篇文章将对Groovy的其中一个核心内容MetaClass(MOP)讲解。由于该部分内容较多。所以还是利用一个个例子逐步的阐述。

 

一、拦截方法调用和参数获取

 

示例1:

 

Java代码   收藏代码
  1. class MyClass{  
  2.     def hello(){  
  3.         'invoked hello directly'  
  4.     }  
  5.     def invokeMethod(String name, Object args){  
  6.         return "unknown method $name(${args.join(', ')})"  
  7.     }  
  8. }  
  9. def mine= new MyClass()  
  10. assert mine.hello() == 'invoked hello directly'  
  11. 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:

 

Java代码   收藏代码
  1. class MyClass implements GroovyInterceptable{  
  2.     def hello(){  
  3.         'invoked hello() directly'  
  4.     }  
  5.     def invokeMethod(String name, Object args){  
  6.         "invoked method $name(${args.join(', ')})"  
  7.     }  
  8. }  
  9.   
  10. def mine = new MyClass()  
  11. assert mine.hello() == 'invoked method hello()'  
  12. assert mine.foo('Mark',19) == 'invoked method foo(Mark, 19)'  
  13.   
  14. assert mine.&hello() == 'invoked hello() directly'  

 

 该例子和示例1的不同在于实现了一个GroovyInterceptable接口,仔细看下这个接口的描述,可知道实现该接口的类被调用方法时都是默认使用invokeMethod方法,而不管该方法时动态生成或时静态生成;第2点不同是如果要再此情况下调用原有定义号的方法,需要变通的使用'.&'操作符。

 

示例3:

 

Java代码   收藏代码
  1. class MyClass{  
  2.     def greeting = 'accessed greeting directly'  
  3.     Object getProperty(String property){  
  4.         "read from property $property"  
  5.     }  
  6.   
  7.     void setProperty(String property, Object newVlaue){  
  8.         throw new Exception("wrote to property $property")  
  9.     }  
  10. }  
  11. def mine = new MyClass()  
  12. assert mine.greeting == 'read from property greeting'  
  13. try{  
  14.     mine.greeting = 'hi'  
  15. }catch(e){  
  16.     assert e.message == 'wrote to property greeting'  
  17. }  
  18.   
  19. assert mine.@greeting == 'accessed greeting directly'  

该示例描述了属性的获取特性,在示例1中已经描述设置和获取属性的方法时继承来的,这里不做赘述。默认的通过Gpath(见Gpath具体概念)来处理属性的值,都是通过调用getProperty和SetProperty来代劳的。同样的如果你真希望直接访问参数的值,可以变通的使用'.@'操作符来达成。

通过Gpath来获得属性值。无论该属性在类中是否有,都是不会出错的执行那2个方法。但是对于类中不存在的属性,忌使用'.@'操作符,会抛出MissingFieldException。

 

示例4:

 

Java代码   收藏代码
  1. class MyClass implements GroovyInterceptable{  
  2.     def greeting = 'accessed greeting'  
  3.     def id ='White: '  
  4.       
  5.     Object getProperty(String property){  
  6.         try{  
  7.             return this.@id + //access field directly  
  8.                     'indirectly ' +  
  9.                     this.@"$property"  
  10.         }catch(e){  
  11.             return "no such property $property"  
  12.         }  
  13.     }  
  14.       
  15.     def hello(Object[] args){"invoked hello with (${args.join(', ')})"}  
  16.       
  17.     def id(){'Green: '}  
  18.       
  19.     def invokeMethod(String name, Object args){  
  20.         try{  
  21.             return this.&id() + //call method directly  
  22.                     'indirectly ' +  
  23.                     this.&"$name"(args)  
  24.         }catch(e){  
  25.             return "no such method $name"  
  26.         }  
  27.     }     
  28. }  
  29.   
  30. def mine = new MyClass()  
  31. assert mine.greeting == 'White: indirectly accessed greeting'  
  32. assert mine.farewell == 'no such property farewell'  
  33.   
  34. assert mine.hello(1'b'3) == 'Green: indirectly invoked hello with (1, b, 3)'  
  35. assert mine.foo('Mark'19) == 'no such method foo'  
 

 

该示例是对示例 2,3的一个合并,同时他告诉我们我们可以通过操作符'.@' 或者'.&'后使用双引号中定义变量的方法来动态的获取参数或者动态的方法。

 

 

二、MetaClass (描述类的类)

 

示例5:

 

Java代码   收藏代码
  1. public class MyMetaClass extends DelegatingMetaClass{  
  2.     MyMetaClass(Class thisClass){  
  3.         super(thisClass)  
  4.     }  
  5.       
  6.     Object invokeMethod(Object object, String methodName, Object[] arguments){  
  7.         "MyMetaClass: ${super.invokeMethod(object, methodName, arguments)}"  
  8.     }  
  9. }  
  10.   
  11. class A{  
  12.     def bark(){'A: invoked bark()'}  
  13.     def invokeMethod(String name, Object args){  
  14.         "A: missing $name(${args.join(', ')})"  
  15.     }  
  16. }  
  17.   
  18. def amc = new MyMetaClass(A)  
  19. amc.initialize()  
  20. def a = new A()  
  21. a.metaClass = amc  
  22.   
  23.   
  24. assert a.bark() == 'MyMetaClass: A: invoked bark()'  
  25.   
  26. Thread.start {  
  27.     assert a.bark() == 'MyMetaClass: A: invoked bark()'  
  28. }  
  29.   
  30. assert new A().bark() == 'A: invoked bark()'  
  31.   
  32. assert a.bleet() == 'A: missing bleet()'  

 该示例的代码较长,主要的意思是我们可以通过任意Groovy对象中的metaClass属性来为改变该对象的方法调用的行为。Groovy为我们提供了DelegatingMetaClass 来让我们实现该功能。

具体的做法是:首先创建一个自定义的MetaClass类继承于DelegatingMetaClass,同时实现构造方法,以及自定义的方法调用行为(以后的内容将介绍,我们不仅可以改变方法行为)。然后我们可以通过自己创建的metaClass子类来包装你想改变行为的目标类。此处为类A。然后再创建目标类实例,对目标类中得metaClass属性进行设置。随后在该实例上的方法调用将会按照该实例的metaClass属性所对应的处理器,进行处理,该例中是再完成本身方法调用后在前面添加MyMetaClass字符串。

但是值得注意的是,1.对于另外新创建的A实例,如果没有进行metaClass属性赋值将按照原方法定义执行;2.动态定义的

方法不受metaClass方法行为改变的影响。见该示例的最后一行。3.上述的特性在新线程内同样有效。

 

示例6:

 

Java代码   收藏代码
  1. InvokerHelper.instance.metaRegistry.setMetaClass(A, amc)  
 

 

该示例只是对示例5的补充,我们曾提到,我们可以通过为目标类实例设置metaClass属性来让metaClass的行为对该实例生效。但是如果要在类范围内生效的话,就需要通过上面的代码进行注册。这样注册过后,将对目标类的所有,任何时候创建的示例,都赋予metaClass的行为。

这个示例还有一点需要注意。试想这么一种情况,先创建了一个目标类实例,再用示例6的语句注册,再创建一个目标类的实例。metaClass的行为将在哪个对象上生效呢。答案是两者都生效。这点非常关键。一旦进行了类范围metaClass注册,那对于已创建和新创建的对象都生效。

注:对于高版本的groovy InvokerHelper的instance不存在。可以直接调用metaRegistry。

 

示例7:

 

Java代码   收藏代码
  1. import org.codehaus.groovy.runtime.InvokerHelper;  
  2.   
  3. public class MyMetaClass extends DelegatingMetaClass{  
  4.     MyMetaClass(Class theClass){  
  5.         super(theClass)  
  6.     }  
  7.   
  8.     Object invokeConstructor(Object[] arguments){  
  9.         []  
  10.     }  
  11. }  
  12.   
  13. class A{}  
  14.   
  15. def amc = new MyMetaClass(A)  
  16. amc.initialize()  
  17.   
  18. InvokerHelper.metaRegistry.setMetaClass(A,amc)  
  19.   
  20. def a = new A()  
  21. assert a.class == ArrayList  
  22. 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:

 

Java代码   收藏代码
  1. class A{  
  2.     String text  
  3.   }  
  4. def a1= new A(text: 'aBCdefG')  
  5. assert a1.metaClass.adaptee.class == MetaClassImpl  
  6.   
  7. A.metaClass.inSameCase = {-> text.toUpperCase()}  
  8. //triggers conversion of MetaClass of A to ExpandoMetaClass  
  9. //then adds new instance method 'inUpperCase' to class  
  10. //A.metaClass {  }  
  11.   
  12. //  
  13. def a2 = new A(text:'hiJKLmnOp')  
  14. assert a2.metaClass.adaptee.class == ExpandoMetaClass  
  15. //MetaClass of A changed for instances created after conversion trigger only  
  16. assert a2.inSameCase() == 'HIJKLMNOP'  
  17.   
  18. //new method not available  
  19. assert a1.metaClass.adaptee.class == MetaClassImpl  
  20. try{ println a1.inSameCase();}  
  21. catch(e){assert e in MissingMethodException}  
  22.   
  23. A.metaClass.inLowerCase = {-> text.toLowerCase()}  
  24. assert a2.inLowerCase() == 'hijklmnop'  
  25.   
  26. //replace the method definition with another  
  27. A.metaClass.inSameCase = {-> text.toLowerCase()}  
  28. assert a2.inSameCase() == 'hijklmnop'  
  29.   
  30. //add static methods  
  31. A.metaClass.'static'.inSameCase = {it.toLowerCase()}  
  32. 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:

 

Java代码   收藏代码
  1. class A{  
  2.   
  3. }  
  4.   
  5. A.metaClass.character = 'Cat in the Hat'  
  6.   
  7. def a1 = new A()  
  8. assert a1.character == 'Cat in the Hat'  
  9.   
  10. def ourProperties = Collections.synchronizedMap([:])  
  11. A.metaClass.setType = {String value -> ourProperties["${delegate}Type"] = value }  
  12. A.metaClass.getType = { -> ourProperties["${delegate}Type"]}  
  13. a1.type = 'Hatted Cat'  
  14. assert a1.type == 'Hatted Cat'  
  15.   
  16. def a2 = new A()  
  17. A.metaClass.constructor = { -> new A()}  
  18. try{  
  19.     a2 = new A()  
  20. }catch(Error e){  
  21.     assert e in StackOverflowError  
  22. }  
  23.   
  24. A.metaClass.constructor = {String s -> new A(character :s)}  
  25. a2 = new A("Thing One")  
  26.   
  27. A.metaClass.'changeCharacterToThingTwo'= {-> delegate.character = 'Thing Two' }  
  28. a2.character= 'Cat in the Hat'  
  29. a2.changeCharacterToThingTwo()  
  30. assert a2.character == 'Thing Two'  
  31.   
  32. ['Hatted Cat''Thing''Boy''Girl''Mother'].each{p->  
  33.   A.metaClass."changeTypeTo${p}"= {-> delegate.type= p}  
  34. }  
  35. a2.changeTypeToBoy()  
  36. assert a2.type == 'Boy'  
  37.   
  38. a2.'changeTypeToHatted Cat'()  
  39. assert a2.type == 'Hatted Cat'  
 

 

该示例的内容只要是示例8的一个补充,我们不仅可以动态添加方法,同时还可以动态添加属性和构造方法。

 

示例10:

 

Java代码   收藏代码
  1. ExpandoMetaClass.enableGlobally()  
  2. //call 'enableGlobally' method before adding to supplied class  
  3. List.metaClass.sizeDoubled = {-> delegate.size() * 2 }  
  4. //add method to an interface  
  5. def list = [] << 1 << 2  
  6. assert list.sizeDoubled() == 4  

 该示例比较简介,旨在告诉我们我们不仅可以对自定义的groovy对象进行属性方法等的动态添加,同样的我们可以对非自定义的Groovy提供的对象进行动态处理。处理方法和自定义对象的方法时完全一样的,唯一的区别在于,我们需要额外定义ExpandoMetaClass.enableGlobally(),然而笔者发现笔者使用的1.8.1版的groovy如果去掉了该声明也是可以工作

所以请各位读者按照个人版本做尝试。

 

示例11:

Java代码   收藏代码
  1. class Bird{  
  2.     def name = 'Tweety'  
  3.     def twirp(){ 'i taught i saw a puddy cat' }  
  4. }  
  5.   
  6. Bird.metaClass.invokeMethod = {name, args->  
  7.     def metaMethod = Bird.metaClass.getMetaMethod(name, args)  
  8.     metaMethod?metaMethod.invoke(delegate,args): 'no such method'  
  9. }  
  10.   
  11. def a = new Bird()  
  12. assert a.twirp() == 'i taught i saw a puddy cat'  
  13. assert a.bleet() =='no such method'  
  14.   
  15. Bird.metaClass.getProperty = {name->  
  16.     def metaProperty = Bird.metaClass.getMetaProperty(name)  
  17.     metaProperty?metaProperty.getProperty(delegate): 'no such property'   
  18. }  
  19. def b = new Bird()  
  20. assert b.name == 'Tweety'  
  21. assert b.filling == 'no such property'  

 该示例主要说明的是我们不仅可以用Expando特性来添加方法属性和构造方法,同样的我们可以对已经存在的方法进行覆盖。最后还要强调一下Bird.metaClass返回的是ExpandoMetaClass我们这里覆盖的getMetaMethod和

getProperty以及invokeMethod和getProperty方法都是对ExpandoMetaClass类和它父类对应的方法进行覆盖。

 

 

至此,Groovy的MetaClass内容已经介绍完了。该部分内容在groovy中非常重要。谨此记录下来作为日后参考,同时希望对大家有帮助。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值