Reputation: 727
I'm trying to add a new method to my Groovy class dynamically, following the documentation. So here is my class which implements the methodMissing method:
class AlexTest {
def methodMissing(String name, args){
println "Method missing is called"
def cachedMethod = { Object[] varArgs ->
println "Hi! ${varArgs}"
}
AlexTest.metaClass."${name}" = cachedMethod
return cachedMethod(args)
}
}
and here is another groovy script which uses my AlexTest class:
def alexTest = new AlexTest()
alexTest.hi("Alex")
alexTest.hi("John")
I expect the "Method missing is called" to be called only once - as the method "hi" would have been 'introduced' inside the methodMissing. But, that methodMissing is being called twice, as if the "hi" method never gets introduced to the AlexTest class.
I have also tried to do it differently:
class AlexTest {
AlexTest() {
def mc = new ExpandoMetaClass(AlexTest, false, true)
mc.initialize()
this.metaClass = mc
}
def methodMissing(String name, args){
println "Method missing is called"
def cachedMethod = { Object[] varArgs ->
println "Hi! ${varArgs}"
}
// note that this is calling metaClass inside the instance
this.metaClass."${name}" = cachedMethod
return cachedMethod(args)
}
}
which sort of works, but only for that single instance. So if I create two instances of AlexTest, and call the 'hi' method, I will get two "Method missing is called" message. Can anyone point me to the documentation which explains this behaviour?
Thanks in advance!
Regards,
Alex
Upvotes: 1
Views: 5360
Reputation: 3021
Another solution is caching the method closures in a Map
.
class AlexTest {
static Map methods = [:]
def methodMissing(String name, args){
if (!methods[name]) {
println "Method is not cached"
methods[name] = { Object[] varArgs ->
println "Hi! ${varArgs}"
}
}
return methods[name](args)
}
}
def alexTest = new AlexTest()
alexTest.hi("Alex")
alexTest.hi("John")
Output
Method is not cached
Hi! [Alex]
Hi! [John]
Upvotes: 0
Reputation: 50265
How about this in the script? This makes sure the methodMissing is implemented for the Class reference and is applied to all of the instances referring to it.
class AlexTest {
}
mc = AlexTest.metaClass
mc.methodMissing = {String name, args=[:] ->
println "Method missing is called"
def cachedMethod = { Object[] varArgs ->
println "Hi! ${varArgs}"
}
mc."${name}" = cachedMethod
cachedMethod(args)
}
def alexTest = new AlexTest()
alexTest.hi("Alex")
alexTest.hi("John")
def myTest = new AlexTest()
myTest.hi("Walter")
myTest.hi("Hank")
//Prints
Method missing is called
Hi! [Alex]
Hi! [John]
Hi! [Walter]
Hi! [Hank]
Although enabling ExpandoMetaClass globally works as well with a cost of little extra memory. :)
Upvotes: 1
Reputation: 24498
Add the following to the beginning of your first attempt:
ExpandoMetaClass.enableGlobally()
It works for me with V 2.0.1
If you prefer to enable on a class instance basis, this post illustrates one way to do it:
class AlexTest {
AlexTest() {
def mc = new ExpandoMetaClass(AlexTest, false, true)
mc.initialize()
this.metaClass = mc
}
def methodMissing(String name, args){
println "Method missing is called"
def cachedMethod = { Object[] varArgs ->
println "Hi! ${varArgs}"
}
this.metaClass."${name}" = cachedMethod
return cachedMethod(args)
}
}
def alexTest = new AlexTest()
alexTest.hi("Alex")
alexTest.hi("John")
Upvotes: 2