I am trying to understand how invokeMethod intercepts method calls in Groovy. I can't seem to get the most basic of examples working though.
class Person implements GroovyInterceptable {
def invokeMethod(String name,args) {
println "called invokeMethod $name $args"
}
def greet() {
println "Hello from greet()"
}
}
def p = new Person()
p.greet()
If I try and run this example I get the following error. What am I missing?
Caught: java.lang.StackOverflowError
java.lang.StackOverflowError
at Person.invokeMethod(Person.groovy:4)
at Person.invokeMethod(Person.groovy:4)
at Person.invokeMethod(Person.groovy:4)
...
You need to invoke the actual method from invokeMethod after the interception.
class Person implements GroovyInterceptable {
def invokeMethod(String name,args) {
System.out.println "called invokeMethod $name $args"
metaClass.getMetaMethod(name, args).invoke(this, args)
}
def greet() {
System.out.println "Hello from greet()"
}
}
def p = new Person()
p.greet()
And yes you are correct about println. Have to use SOP.
Related
I'm seeing a rather strange thing with the new Groovy trait implementation. I have a (single method interface) class that implements a call() method so I can call call its only method just like a closure: instance() instead of having to do instance.call() . However as soon as I introduce such a reference (a Callable) using a trait that breaks. Anyone who can give a hint if this is something that is as expected or is there a workaround to that?
class MethodCallSpec extends Specification {
def "call a callable"() {
expect:
def thing = new Callable()
"hello" == thing.call("hello")
}
def "call a callable like a closure"() {
expect:
def thing = new Callable()
"hello" == thing("hello")
}
def "call with callable"() {
expect:
def thing = new SomethingWithCallable()
"hello" == thing.doSomething("hello")
}
}
class Callable {
Object call(def message) {
message
}
}
trait WithCallable {
Callable callable = new Callable();
}
class SomethingWithCallable implements WithCallable {
def doSomething(def message) {
callable(message) // this breaks unless written as callable.call(message)
}
}
I have the following two Groovy classes:
Buzz.groovy:
import widgets.Fizz
class Buzz {
def WhistleFeather
def init = { servletContext ->
WhistleFeather.load()
println "WhistleFeather loaded."
}
def destroy = { }
}
WhistleFeather.groovy:
package net.me.myapp
import widgets.Fizz
public class WhistleFeather {
def navMenu
def load() {
println "Loading!"
}
}
When execution gets to the WhistleFeather.load() method call I'm getting a NullPointerException. Can anyone see why?
WhistleFeather#load is an instance method, not a static method. Either call it with new WhistleFeather().load(), or turn it into a static method (static load() { ... }).
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
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")
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. :)
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]
Since every Groovy object implements GroovyObject interface, i would try to override invokeMethod(), here is my test :
class MyGrrovyClass {
static test(){
println 'i am in test'
}
Object invokeMethod(String name, Object args){
log.info('method intercepted')
def metaClass = InvokerHelper.getMetaClass(this)
def result = metaClass.invokeMethod(this, name, args)
return result
}
public static void main(String[] args) {
test()
}
}
but it seems doesn't work, i've never seen log message in my console
My second question is : GroovyInterceptable is the subinterface of GroovyObject, what the difference between that i override directly invokeMethod of GroovyObject and i implement invokeMethod of GroovyInterceptable interface?
thanks
According to the documentation (http://groovy.codehaus.org/Using+invokeMethod+and+getProperty) you must implement GroovyInterceptable to intercept existing methods I think this answers your first and second questions!
I made some slight changes to get your sample class working although was surprised to see that my println was intercepted but not System.out.println - this meant that I was getting a stack overflow because I originally had a simple println in the invokeMethod and that was getting recursively called.
class MyGrrovyClass implements GroovyInterceptable {
def test(){
println 'i am in test'
}
def invokeMethod(String name, args){
System.out.println('method intercepted: '+ name)
def result= metaClass.getMetaMethod(name, args).invoke(this, args)
}
}
def mgc= new MyGrrovyClass()
mgc.test()
I'm trying to enhance my Grails project with Groovy AOP approach. However I always got StackOverflowError if I override invokeMethod with a closure. Here is my test code, I can reproduce the error with groovy 2.1.3, thanks!
class A implements GroovyInterceptable
{
void foo(){
System.out.println( "A.foo");
}
}
class B extends A
{
void foo(){
System.out.println( "B.foo");
super.foo();
}
}
def mc = B.metaClass;
mc.invokeMethod = { String name, args ->
// do "before" and/or "around" work here
try {
def value = mc.getMetaMethod(name, args).invoke(delegate, args)
// do "after" work here
return value // or another value
}
catch (e) {
// do "after-throwing" work here
}
}
B b = new B();
b.foo();
Looks like, if you have a call to super() then metaClass uses the cache to find the method and throws a StackOverflow eventually. In that case if you metaClass A instead of B, it all works fine.
def mc = A.metaClass
I can infer it this way, class implementing GroovyInterceptable directly should override invokeMethod.
#Source MetaClassImpl