Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy - Add property or method dynamically to metaClass of this

Tags:

groovy

I want to dynamically add a field and methods to the metaClass of my current object. I tried

this.metaClass.testProp = "test"

to add a field called testProp. However, I get

groovy.lang.MissingPropertyException: No such property: testProp for class: groovy.lang.MetaClassImpl

When I do the same on class level, e.g. adding testProp to the class and not directly to the object

Process.metaClass.testProp = "test"

it works, but my object does NOT inherit the field. Any ideas or pointers in the right direction would be greatly appreciated.

like image 447
WeSt Avatar asked Sep 05 '15 10:09

WeSt


1 Answers

Short answer:

Process.metaClass.testProp = "test"
this.metaClass = null
assert this.testProp == "test"

Long answer:

I assume, there are 3 things that make you a problem here. The first one is that there is a global registry for meta classes. The second is that Groovy allowed per instance meta classes (and this is the default for Groovy classes). And the third one is that the default meta class does not allow runtime meta programming.

So what happens if you do runtime meta programming is that the default needs to replaced by ExpandoMetaClass (EMC), to allow for that. But since there is a per instance meta class logic, this replacement may not affect all instances. The meta class of the instance is taken from the global registry the first time it is used. Process.metaClass.testProp = "test" changes the global saved one. Any instance that already has a meta class, will not know of the change and thus not know the property. this.metaClass.testProp = "test" will change only the meta class of the current instance, other instances may not know of it. You can use this.metaclass = null to reset it. And it will again request the meta class from the global registry.If you did the per instance change, your change is away. If you did the global change, your change is visible now.

All this counts if you work with the default meta class in Groovy (MetaClassImpl). If you do a change like a in your code, the new meta class will be ExpandoMetaClass (EMC). This meta class allows for changes, so further changes will not cause a replacement of the meta class. To ensure all instance take an ExpandoMetaClass from the beginning you normally have some kind of setup code like this: ExpandoMetaClass.enableGlobally()

So to solve your question I could simply do this

Process.metaClass.testProp = "test"
this.metaClass = null
assert this.testProp == "test"

If you used ExpandoMetaClass.enableGlobally() in some setup code before, you can leave the reset code for the meta class out (or if the global meta class is the same as the one for "this" and if it is EMC). Grails for example uses EMC as default

like image 88
blackdrag Avatar answered Oct 31 '22 14:10

blackdrag