Groovy the language offers neat metaprogramming features. To metaprogram means to write programs manipulating other programs. In context of groovy this enables us to synthesize methods and classes, intercept calls and make objects behave like instances of different types. All of these manipulations happen at runtime. Groovy also offers compile-time AST (Abstract Syntax Tree) manipulation.
Before we move out to the features themselves, let’s start with figuring out how Groovy can change behavior of various classes of objects.
Metaclasses #
When we create an object in JVM its class is fixed and can’t be
changed at runtime. Groovy introduces the concept of
metaclasses. Metaclass
is a class-like object specific
to Groovy. Contrary to the regular Java Class
object, it does
not bear a special meaning and can be changed at leisure.
Before invoking a method on an object, Groovy first consults its metaclass. So changing or extending a metaclass makes an impression of object changing its class at runtime. But where the metaclass is stored? To find out we need to explore various kinds of object first.
We can have following kinds of objects in Groovy:
Plain Old Java Objects (POJOs) - instances of regular java objects created on JVM.
Plain Old Groovy Objects (POGOs) - subclasses of
GroovyObject
. An interface defined as follows.
public interface GroovyObject { Object invokeMethod(String name, Object args); Object getProperty(String property); Object setProperty(String property, Object newValue); MetaClass getMetaClass(); void setMetaClass(MetaClass metaclass); }
- Groovy Interceptors - subclasses of
GroovyInterceptable
public interface GroovyInterceptable extends GroovyObject {}
Metaprogramming is available for POJOs and POGOs. Groovy interceptors have their own way of achieving similar flexibility.
With a POGO it is simple. You need to call its setMetaClass
method
and a reference to this metaclass is stored within the object. With
POJO this is impossible - they are not designed to store a metaclass
reference. For this reason Groovy maintains an application wide
MetaClassRegistry
which maps java.lang.Class
es to
metaclasses.
Method Resolution Order #
We know that each POJO and POGO has an associated metaclass (either holds the reference directly or has it stored in application-wide registry).
POJO #
If you invoke a method on POJO then Groovy first inspects its metaclass. If the method exists in the metaclass it takes precedence over the regular one.
An example is given below.
// PlainOldJavaClass.java public class PlainOldJavaClass { public void pojoOnly() { System.out.println("From PlainOldJavaClass"); } public void alsoInMetaClass() { System.out.println("From PlainOldJavaClass"); } }
// Runner.groovy // Set-up metaclass PlainOldJavaClass.metaClass.alsoInMetaClass = { println 'From metaclass' } PlainOldJavaClass.metaClass.metaclassOnly = { println 'From metaclass' } PlainOldJavaClass pojo = new PlainOldJavaClass(); pojo.pojoOnly() pojo.alsoInMetaClass() pojo.metaclassOnly()
This prints:
From PlainOldJavaClass From metaclass From metaclass
We can see that the metaclass methods take precedence. A regular method is even not required.
POGO #
With POGOs there are more steps. If you want to invoke a method the steps are as follows.
- If the method exists in metaclass it is invoked;
- If the method exists in regular class it is invoked;
- If there is a field with method name and this field holds a reference to a closure then the closure is called;
- If there is a
methodMissing()
then it is called; - If there is
invokeMethod()
then it is called; - Else,
MethodMissingException
is thrown.
A simple example.
// CoolNewGroovyObject.groovy class CoolNewGroovyObject { void regularMethod() { println "GroovyObject#regularMethod" } def closureProperty = { println "GroovyObject#property" } Object methodMissing(String name, Object args) { println "Method missing: ${name}" } def regularProperty = "This is a nice String" }
// Runner.groovy GroovyObject.metaClass.metaMethod = { println 'From metaclass' } GroovyObject go = new CoolNewGroovyObject() go.metaMethod() go.regularMethod() go.closureProperty() go.regularProperty()
This prints:
From metaclass GroovyObject#regularMethod GroovyObject#property Method missing regularProperty
Groovy Interceptors #
Groovy Interceptors are simply POGOs implementing the
GroovyInterceptable
marker interface.
In case of the interceptors all method invocations (both existing and non-
existing) are routed to invokedMethod()
.
Summary #
We examined various types of objects available in Groovy, we also found out how the method invocations are dispatched to concrete method definitions. With this knowledge we should be able to tell which methods gets invoked when.
>> Home