Saturday, March 22, 2008

Simple method events with Groovy meta-programming

Groovy has an amazing meta-programming capability and the behavior can be changed easily if you know where to look.

Lets start with a simply concept of method event -- you can think of it as a primitive form of AOP if you want to -- and we want to automatically call a before* and after* methods on a method (huh?).

Example;

class Person {
String name

String toString() {
return name
}
}


A simple before and after event for toString method will be beforeToString() and afterToString().

With Groovy we can make use of its metaClass to intercept and modify its method behaviour. In this case we want to intercept all method call and check if it has any defined before and after event methods available and execute this methods.

Lets go back and define the before and after method for toString;

class Person {
String name

String beforeToString() {
}

String toString() {
return name
}

String afterToString() {
}

}

So now we have defined 2 before and after event method,
lets look at the basic groovy code to make all these happen.

Person.metaClass.invokeMethod = { String methodName, args ->
println "[invokeMethod] ${methodName}() called with ${args}"

def metaClass = Person.metaClass
def method = methodName.getAt(0).toUpperCase() + methodName.substring(1)

def beforeMethod = metaClass.getMetaMethod("before${method}", args)
def afterMethod = metaClass.getMetaMethod("after${method}", args)

if (beforeMethod) beforeMethod.invoke(delegate, args)

def metaMethod = metaClass.getMetaMethod(methodName, args)
def result
if (metaMethod) result = metaMethod.invoke(delegate, args)

if (afterMethod) afterMethod.invoke(delegate, args)

return result
}

First we would have to look for the existence of before/after methods.
This is done at this line:

def beforeMethod = metaClass.getMetaMethod("before${method}", args)

Then we execute them in the correct order:

if (beforeMethod) beforeMethod.invoke(delegate, args)

Finally, we execute the real method and return the result:

def metaMethod = metaClass.getMetaMethod(methodName, args)
def result
if (metaMethod) result = metaMethod.invoke(delegate, args)

Of course this is the very rough and basics of the method event and you still have to consider about cyclic-method-call prevention, continuation of method call, and proper return of method value.

3 comments:

Dmitriy Kopylenko said...

Should have been: Person.metaClass instead of: Controller.metaClass

Seymour Cakes said...

Ops, thanks! Copy-n-paste is bad.
Cheers

Justin said...

Groovy's invoke method is great for aspect oriented like programming. Thanks for the write up.