Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy: this.metaClass Versus instance.metaClass

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:

  1. why is this.metaClass not equal to instance.metaClass?
  2. 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.
  3. 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)

like image 809
Guocheng Avatar asked Dec 24 '15 08:12

Guocheng


1 Answers

  1. 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.

  2. 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)
like image 98
Guocheng Avatar answered Oct 11 '22 18:10

Guocheng