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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With