18Jun
2008

Tags:

Aspect Oriented Programming (AOP) is a technique that can be used to eliminate the repetition of cross-cutting concerns (logging, security, transaction management, etc.) in code. AOP is useful because it provides programmers with a single point at which code can be modified and take effect across an entire system, but integrating AOP frameworks into traditional Java applications tends to be somewhat cumbersome and complex. Conversely, adding AOP-like features into Groovy code is as simple as implementing the GroovyInterceptable interface.

Any Groovy object that implements this interface will automatically have all of its method calls routed through its invokeMethod() (assuming of course that this method has been implemented on the Groovy object itself or on its MetaClass).

A Simple Example

Let's take a look at a quick example. Assume that for whatever reason we want to know the time before and after each call to a method in a class called SimplePOGO. To accomplish this task in Groovy, we simply implement the GroovyInterceptable interface and the invokeMethod() like this:

class SimplePOGO implements GroovyInterceptable {
    void simpleMethod1(){
        System.out.println("simpleMethod1() called")
    }

    void simpleMethod2(String param1, Integer param2){
        System.out.println("simpleMethod2(${param1},${param2}) called")
        System.out.println("sleeping...")
        Timer.sleep(2000)
    }

    def invokeMethod(String name, args){
        System.out.println("time before ${name} called: ${new Date()}")

        //Get the method that was originally called.
        def calledMethod = SimplePOGO.metaClass.getMetaMethod(name, args)

        //The "?" operator first checks to see that the "calledMethod" is not
	//null (i.e. it exists).
        calledMethod?.invoke(this, args)

        System.out.println("time after ${name} called: ${new Date()}\n")
    }
}

simplePogo = new SimplePOGO()
simplePogo.simpleMethod1()
simplePogo.simpleMethod2("stringParam", 24)
	

This code will produce the following output:

time before simpleMethod1 called: Wed Jun 18 12:07:06 EDT 2008
simpleMethod1() called
time after simpleMethod1 called: Wed Jun 18 12:07:06 EDT 2008

time before simpleMethod2 called: Wed Jun 18 12:07:06 EDT 2008
simpleMethod2(stringParam,24) called
sleeping...

time after simpleMethod2 called: Wed Jun 18 12:07:08 EDT 2008

Because SimplePOGO implements GroovyInterceptable all calls to its methods will first be routed through its invokeMethod(). Within the invokeMethod() we print the current time, look up the actual method that was called, call that method, and then print out the current time after execution.

An Example with Nested Methods

What happens if we nest methods? Take a look at this example:

class SimplePOGO implements GroovyInterceptable {

    void simpleMethod1(){
        System.out.println("simpleMethod1() called")
        simpleMethod2Nested("stringParam", 24)
    }

    void simpleMethod2Nested(String param1, Integer param2){
        System.out.println("simpleMethod2Nested(${param1},${param2}) called")
        System.out.println("\nsleeping...\n")
        Timer.sleep(2000)
    }

    def invokeMethod(String name, args){
        System.out.println("time before ${name} called: ${new Date()}")

        //Get the method that was originally called.
        def calledMethod = SimplePOGO.metaClass.getMetaMethod(name, args)

        //The "?" operator first checks to see that the "calledMethod" is not
	//null (i.e. it exists).
        calledMethod?.invoke(this, args)

        System.out.println("time after ${name} called: ${new Date()}")
    }
}

simplePogo = new SimplePOGO()
simplePogo.simpleMethod1()
	

This code will produce the following output:

time before simpleMethod1 called: Wed Jun 18 12:07:35 EDT 2008
simpleMethod1() called
time before simpleMethod2Nested called: Wed Jun 18 12:07:35 EDT 2008
simpleMethod2Nested(stringParam,24) called

sleeping...

time after simpleMethod2Nested called: Wed Jun 18 12:07:37 EDT 2008
time after simpleMethod1 called: Wed Jun 18 12:07:37 EDT 2008

Not too surprising. When simpleMethod1() is called, the invokeMethod() in SimplePOGO is called which prints the current time stamp and then executes the simpleMethod1(). Notice however that simpleMethod1() calls simpleMethod2Nested(). When this call is made invokeMethod() will again be called so that the "before" time stamp will be printed out again. When simpleMethod2Nested() finishes executing an "after" time stamp is printed to the screen and control returns to simpleMethod1() which also finishes executing and prints the current time to the screen.

Gotcha?

You may have noticed that I used System.out.println() instead of the Groovier println() to print the output to the screen. This is because println() is a method that is built into the GroovyObject which is extended by all other Groovy objects (including SimplePOGO). If a call to println() is made within one of SimplePOGO's methods then the invokeMethod() on SimplePOGO will be called again which may lead to output that is not quite expected:

class SimplePOGO implements GroovyInterceptable {

    void simpleMethod1(){
        println "simpleMethod1() called"
    }

    void simpleMethod2(String param1, Integer param2){
        println "simpleMethod2(${param1},${param2}) called"
        println "sleeping..."
        Timer.sleep(2000)
    }

    def invokeMethod(String name, args){
        System.out.println("time before ${name} called: ${new Date()}")

        //Get the method that was originally called.
        def calledMethod = SimplePOGO.metaClass.getMetaMethod(name, args)

        //The "?" operator first checks to see that the "calledMethod" is not
	//null (i.e. it exists).
        calledMethod?.invoke(this, args)

        System.out.println("time after ${name} called: ${new Date()}")
    }
}

simplePogo = new SimplePOGO()
simplePogo.simpleMethod1()
simplePogo.simpleMethod2("stringParam", 24)
	

This code will produce the following output:

time before simpleMethod1 called: Wed Jun 18 12:13:32 EDT 2008
time before println called: Wed Jun 18 12:13:32 EDT 2008
simpleMethod1() called
time after println called: Wed Jun 18 12:13:32 EDT 2008
time after simpleMethod1 called: Wed Jun 18 12:13:32 EDT 2008
time before simpleMethod2 called: Wed Jun 18 12:13:32 EDT 2008
time before println called: Wed Jun 18 12:13:32 EDT 2008
simpleMethod2(stringParam,24) called
time after println called: Wed Jun 18 12:13:32 EDT 2008
time before println called: Wed Jun 18 12:13:32 EDT 2008
sleeping...
time after println called: Wed Jun 18 12:13:32 EDT 2008
time after simpleMethod2 called: Wed Jun 18 12:13:34 EDT 2008

Worse still is if you try to place the println() method inside of the invokeMethod() itself. This will lead to recursive calls and an eventual StackOverflowError:

class SimplePOGO implements GroovyInterceptable {

    void simpleMethod1(){
        System.out.println("simpleMethod1() called")
    }

    def invokeMethod(String name, args){
    	try {
	    println("time before ${name} called: ${new Date()}")

	    //Get the method that was originally called.
	    def calledMethod = SimplePOGO.metaClass.getMetaMethod(name, args)

	    //The "?" operator first checks to see that the "calledMethod" is
            //not null (i.e. it exists).
	    calledMethod?.invoke(this, args)

	    println("time after ${name} called: ${new Date()}\n")
	} catch (StackOverflowError soe) {
	    System.out.println("A StackOverflowError was caught")
	    System.exit(0)
	}
    }
}

simplePogo = new SimplePOGO()
simplePogo.simpleMethod1()
	

This code will produce the following output:

A StackOverflowError was caught

Conclusion

The examples listed above are very simple, but they illustrate how easy it is to implement the powerful features of AOP in Groovy. It's not too difficult to envision how such a capability could be built into an application to handle a lot of the boiler plate code that tends to crowd non AOP applications.

Share and Enjoy:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • DZone
  • FSDaily
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  1. 2 Responses to “Easy AOP with GroovyInterceptable”

  2. I made the mistake of putting the simpler println in the invoke method so thank you for the gotcha. However, I did not receive a StackOverflowError, instead the program just nulled out.

    By KevinO on Aug 11, 2011

  3. Thanks for sharing. Very nice.

    By vasya10 on Feb 15, 2012

Post a Comment