Cedric Beust described Aspect Oriented Programming (AOP) as "a great idea that will remain the privilege of a few expert developers". For some, even with Spring and JBoss, the barriers to entry remain too high. Fortunately, this is an area where the dynamic languages may be able to help out. They can provide either a gentle nursery slope for experimentation and learning, prior to the red runs of AspectJ, or a highly productive work environment in their own right. Java developers need not even stray far from home. Groovy, a JVM dynamic language with a Java-like syntax, sports impressively powerful features that make mimicking AOP a breeze. Although we will focus on Groovy in this article, the zeitgeist demands a comparison with the much loved and feared Ruby. Rubyists can also get 80% of the functionality with 20% of the effort.
AOP allows us to modularise code that may otherwise remain entangled across a number of methods and classes. For example, we may find ourselves doing the same security check around a number of different units of work, instantiating some variables before other units and logging some information after yet others have completed or thrown an exception. AOP allows us to write all this functionality once and have it applied to our methods at the appropriate point (before, after, around or when an exception is thrown), where once we may have violated the DRY principle and written it multiple times. Typically, in Java, we would use one of two mechanisms. Either we would use a framework to modify the byte-code of our own classes to directly insert this functionality, or we would dynamically create a proxy for our own classes (either using JDK Dynamic Proxies or CGlib). In Groovy, as we shall see, we need neither technique.
MOPs : not just for cleaning!
Groovy's Metaobject-Protocol is a mechanism by which application developers can modify the implementation and behaviour of their language. Groovy's MOP is particularly powerful. The result of judicious design, all dynamic method calls get routed through invokeMethod and property access through getProperty and setProperty. Thus, we have a single point of contact for modifying the core behaviour of the Objects we create. By overriding invokeMethod, getProperty and setProperty we can intercept every single call to our Objects without the need for a proxy or byte-code manipulation.
Free the functions!
"Would you be willing to trade all the days from this day to that for one chance, just one chance, to come back here and tell our enemies that they may take our lives, but they'll never take our freedom." - Mel Gibson rouses the Scots in Braveheart
Language aficionado Steve Yegge, humourously labelled Java the "Kingdom of the Nouns", a land where functions are enslaved and may only travel attached to a noun. Groovy frees functions from the tyranny of Object bound slavery. A Groovy Closure is an anonymous function that can access its enclosing scope, can be called repeatedly at will, and be passed around as it were data. Syntactically closures are defined in what look like Java code blocks. The example below prints out "hello world from Groovy!".
String printMe = "hello world"
def c = { parameter | println(printMe + parameter} }
c(" from Groovy!")
The ability to create such lightweight functions combined with the power of the Groovy MOP will enable us to implement some AOP-style magic with very little code.
Before we start
If you want to follow the examples below, you'll need to follow these steps:
- Download Groovy :
Groovy can be downloaded from the Groovy homesite - Follow the installation instructions on the Groovy installation page
- Optionally, download and install the Groovy Eclipse Plugin. The update site for the plugin is http://dist.codehaus.org/groovy/distributions/update/. There is a Wiki site with some additional information here. The Groovy Eclipse plugin is still pre-release, but, with a bit of tinkering, I find it works well. You may need to take into account the following :-
-
Setup your step filters : In preferences/java/debug/stepfiltering, turn on step filtering for the following classes:-
Groovy does a lot of work under the covers and this prevents the Eclipse plugin taking you through the Groovy infrastructure as you execute your code.
- You step into Closures rather than over them
- You can't currently use debug as/groovy directly, instead you must create a new Debug Application configuration. The steps are defined in the wiki.
- In order to get the plugin to find your source you may find that you have to give Eclipse a few additional clues. A work around is to make a copy of your classes directory and add it as the last class folder. Then in project/properties/libraries you can attach your source folder to your dummy class folder.
-
Iteration 1: Let's get started
AOP allows us to execute an arbitrary function when a specified method is called. When we do this, we are said to be applying "advice". Before and after advice types are particularly easy to implement in Groovy.
Define the infrastructure
First, let's write a Groovy class that can hold the details of a method call that has just been intercepted. We will pass this Object to all our advice implementations as a parameter. This will allow them to "understand" their context, to access and even modify properties on the target Object and the incoming parameters.
N.B. The keyword "def", used here as an instance variable modifier, indicates that the variables are properties. Groovy will add standard JavaBean Getters and Setters for us. Also note that this class extends Expando, as this will be important later.
We can define a class that overrides invokeMethod and inserts optional calls to our before and after advice types. To leverage AOP-like functionality, we need only inherit from this Class. In order to force Groovy to call invokeMethod for each method call to our class we need to implement the marker interface GroovyInterceptable.
In this class the keyword def is being used as a local variable modifier. This indicates that the variable is dynamically typed. We also take advantage of Groovy's convenient parameter naming support to instantiate our AopDetails Object.
This principle can be extended to property access, but, for clarity, I shall leave the implementation of this to your imagination.
We've defined all the necessary infrastructure to get started, so let's demonstrate some AOP in action with an example.
An example in action
We need a main method to apply advice to our Example class, and print out some helpful status messages.
If you are following along with the Groovy Eclipse plugin, you can a breakpoint at the start of the main method and step through the code. We can execute Runner.groovy by right clicking on the file and selecting run-as -> groovy from the popup menu, to debug follow the steps above & in the wiki.The output from executing Runner.groovy is as follows
Calling print message with no AOP :-
Calling print message with AOP before advice to set message :-
hello world
Calling print message with AOP after advice to output status :-
hello world
Status:message displayed
Calling print message via invokeMethod on a Statically defined Object :-
hello world via invokeMethod
Initially, we call printMessage with a Map that contains an empty String under the key "message". However, prior to the next call to printMessage we declare some before advice to be applied to printMessage that changes the message to "hello world". Next, we apply some after advice to output a status message. We can set a breakpoint in invokeMethod to see clearly see what is happening.
"invokeMethod" checks for a Closure to execute with the current method name in the before and after maps. If it finds one - it executes it at the appropriate point.
This trivial example demonstrates principles that can be readily applied to more complex, real world situations.
Iteration 2: Around we go
So far, so straight forward. For the next iteration we'll add in some around advice. Around advice is a more powerful advice type, in that it can modify the flow of the application - it has the power to decide whether or not our target method even get's executed. As such, it is an excellent place to implement security features.
Define the infrastructure
We need to modify our infrastructure to cope with the new Advice type. We don't need to change AopDetails. When designing AopDetails we used Expando as the base class. Expando is a special Groovy Object that leverages similar MOP trickery to provide an expandable Object. We need to add a proceed() method to AopDetails so that our Around advice can call the target method, with Expando, this is no problem. Let's take a look at our new AopObject:
We've added a map to hold our around advice Closures. The continueWith Closure calls the target method, and we temporarily add it to our AopDetails Object as the proceed method. If a suitable around advice exists we call it, otherwise we continue directly to our target method.
Another example in action
Let's make our example a little more real world this time. We will attempt to simulate a web controller.
Our web controller implements a "save" method, and we've mocked up a model Object using powerful Groovy features (Expando and Closures). We've added two functions to our model - params, which loads parameters from the request and save which "saves" the model. As the save action may be performing a sensitive update, it might be a good idea to introduce some security features around it.
The Runner class mocks up Request and Session Objects using Maps. We initially call save on the controller without any security features, but for the subsequent call we insert some around advice that checks the user id stored in the session. Finally, we demonstrate that if the user id is correct the original method can indeed proceed. The output is as follows: -
Attempting initial save : no AOP security:-
Saved model 10 successfully
Attempting save with AOP security & incorrect id:-
Sorry you do not have permission to save this model (10)
Attempting save with AOP security & correct id:-
Saved model 10 successfully
The screenshot shows the debugger at a breakpoint inside the around security Closure in Runner.groovy.
Iteration 3: Reusable methods
With Closures we can define methods independently from Classes. With the MOP, as we have seen with Expando, we can insert those free methods into multiple classes. Heck, we can even inject different methods into different Objects of the same class. This level of modularity is much more fine-grained than standard Java allows. Most of us Java developers are used to injecting services via Object Interface implementations, and we typically use a lot of XML to achieve this!
Define the infrastructure
We can expand our AOP-like infrastructure classes to allow the insertion and deletion of methods from our Objects. We need two new Classes at either end of the inheritance hierarchy to do this. AopIntroductions, which allows us to introduce new methods to our Objects, becomes our base class (AopObject should now extend AopIntroductions). AopRemovals, which prevents calls to specified methods from being executed, is the new top-level class.
Let the games begin!
Now we can have some real fun! We are able to leverage instance methods as reusable components. We can define generic methods in one place and mix them into our Objects across multiple Class types while limiting access to sensitive methods on a per Object basis.
Let's refactor our controller from the previous example by removing the save method. We'll create a CrudOperations class that will be a holder class for crud methods. We'll then inject the methods from our CrudOperations class into our web controller.
Notice the call to the constructor of CrudOperationsMixin, this is where the magic happens!
In the class above we have defined a Map of method references. We're injecting each of those references by their key into the host Object (our web controller).
The beauty is we can still apply around, before and after advice types to our newly added methods! Runner.groovy from the previous example can run as is. However, we'll add another twist by disabling the save method at runtime in a new Class.
Groovy supports operator overloading and the List class uses the left shift operator ("<<") to append new Objects to the List.
The output from running Runner.groovy is as follows:
Attempting initial save : no AOP security:-
save operation
Saved model 10 successfully
Attempting save with AOP security & incorrect id:-
Sorry you do not have permission to save this model (10)
Attempting save with AOP security & correct id:-
save operation
Saved model 10 successfully
Attempting save with save method removed (perhaps for security reasons):-
Caught NoSuchMethodException
In the debugger screenshot below I've highlighted the StackTrace that shows the path from our web controller, where the call to save was made, to our mixins class where the save method actually resides.
From Groovy to Ruby : Alternatives for train drivers
If Groovy isn't to your taste, we could re-implement the above examples in the dynamic language of your choice. For example in Ruby we could override the method_added hook rather than invokeMethod. "method_added" is called when a new method is added to an class. As the methods are added to our Object we can proxy them swapping them out for an implementation that inserts before, after, around advice via alias_method. Even Javascript, once the bane of every web developer's life, has powerful idioms allowing AOP to be implemented easily. There's even a framework for it - AspectJs!
Summary
Dynamic languages like Groovy make implementing custom-AOP infrastructure painless. There is no requirement for byte-code modification, and the infrastructure code base can be kept small so as to be readily understandable by new developers. Groovy's MetaObject-Protocol allows us to mould our Object's behavoiur to suit the problem at hand and first class functions and dynamic typing makes dynamically inserting functionality relatively easy. The examples described here are the first step on the journey towards full AOP functionality. Still left to implement are exception advice, support for the application of multiple advices per method (including advice chaining for arounds) and centralised support for applying Aspects to your Objects. But I hope that I've demonstrated that we can get very far, with relatively little effort, thanks to the power of the Groovy language.
Resources
- Get the code used in this article
- Get Groovy
- The Groovy Eclipse Plugin
- An introduction to AOP in Java
- An introduction to Groovy (quite old)
- Closures in Groovy
- More on Groovy's MOP
- Nothing new under the sun, :around, :before and :after in CLOS 1988.
About the author
John McClean is a software developer living in Dublin, Ireland, where he works for AOL Technologies, Ireland. In his spare time, he develops the Slingshot Application Framework a highly productive environment for developing web-based applications. John maintains a blog where he jots down his thoughts on technical topics of interest.