I have encountered below groovy script code in the book . And it generated some strange outputs to me.
class Person{
def work(){
println "work()"
}
def sports=['basketball','football','voleyball']
def methodMissing(String name, args){
if(name in sports){
println "injected ${name} into Person class"
Person instance=this
println "this.metaClass:\t\t${this.metaClass}"
println "instance.metaClass:\t${instance.metaClass}"
assert this.metaClass==instance.metaClass
}else{
println "no such method:${name}() in Person class"
}
}
}
def jack=new Person()
jack.football()
it's output is as below:
injected football into Person class
this.metaClass: groovy.lang.MetaClassImpl@245b4bdc[class Person]
instance.metaClass: org.codehaus.groovy.runtime.HandleMetaClass@245b4bdc[groovy.lang.MetaClassImpl@245b4bdc[class Person]]
Caught: Assertion failed:
//I did not paste the detailed assertion here for simplicity
So I am quite confused:
- why is this.metaClass not equal to instance.metaClass?
- further more, I can not use this.metaClass to inject new methods; groovy tells me this.metaClass have no such property, which I intended to inject.
- What does "org.codehaus.groovy.runtime.HandleMetaClass@245b4bdc[groovy.lang.MetaClassImpl@245b4bdc[class Person]]" mean? I know "245b4bdc" may be the object pointer. But why HandleMetaClass and MetaClassImpl have the same pointer value "245b4bdc"?
Currently, I figured out that @245b4bdc is not the "Object reference", So HandleMetaClass@245b4bdc is not necessarily the same instance as MetaClassImpl@245b4bdc. We can use Object.is() method to judge whether they are the same.(I did that, result is false)
why this.metaClass != instance.metaClass?
It involves groove's access to fields.
When accessing an instance field from "outside", groovy actually calls the function getFieldName(). In my example, when I use "instance", i am at the outside; So instance.metaClass will call instance.getMetaClass().
When accessing an instance field from "inside", groovy simply directly access the field, getFieldName() is not called. In our example, when I use "this", i am at the "inside"; So "this.metaClass" will access "metaClass" directly.
Finally, getMetaClass() returns a HandleMetaClass object while the internal metaClass is a MetaClassImpl object. So this.metaClass!=instance.metaClass.
Why this.metaClass.say={->println "say"} will throws MissingPropertyException?
this.metaClass's type is MetaClassImpl
MetaClassImpl is a low level class, which supports upper level classes(eg. HandleMetaClass) for injection. It's not meant for Developer to use directly, So it does not support the injection way: xxxx.say={->println "say"}.
Code Sample(For Question 1):
class Person{
def work(){
println "work()"
}
def sports=['basketball','football','voleyball']
def methodMissing(String name, args){
if(name in sports){
Person instance=this
println "this.metaClass:\n\t${this.metaClass}"
println "instance.metaClass:\n\t${instance.metaClass}"
//output: false
println "this.metaClass.is(instance.metaClass):\n\t${this.metaClass.is(instance.metaClass)}"
//output: true
println "this.getMetaClass().is(instance.getMetaClass()):\n\t${this.getMetaClass().is(instance.getMetaClass())}"
}else{
println "no such method:${name}() in Person class"
}
}
}
def jack=new Person()
jack.football()
jack.football()
Code Sample(For Question 2):
class Cat{}
def a=new groovy.lang.MetaClassImpl(Cat)
try{
a.say={->println "say"}
}catch(MissingPropertyException e){
println "[Fail]\n\tcan not inject method say() into MetaClassImpl class.\n"
}
def b=new org.codehaus.groovy.runtime.HandleMetaClass(a)
println b
b.say={->println "[say]"}
println "[OK]\n\tcan inject method say() into HandleMetaClass class\n"
def method=b.getMetaMethod("say")
method.invoke(this)