"If Groovy is too slow for a particular operation, you can make use of Groovy's strong integration with Java (Groovy is Java, after all) and replace the slow Groovy implementation with one that is written in Java and which
should thus be more efficient."
Every book on Groovy includes some variation of this statement…I thought I'd take a quick look and see how this can be done.
There are, of course, many alternatives…most of which rely on some degree of intrusive source code change: alter the class containing the slow method so that it extends a Java class and then remove the offending Groovy method and
implement the replacement in the Java superclass; inject a Java 'helper' class and then change the slow method so that it defers to this helper; and so on.
We're talking Groovy so naturally there is a better, non-intrusive way.
Welcome to the Delegating MetaClass:
The idea is that any package.class can have a custom meta class loaded at startup time by placing it into a well known package with a well known name.
groovy.runtime.metaclass.[YOURPACKAGE].[YOURCLASS]MetaClass
In times of yore, this used to be called a tail patch and was a tool in every wild-eyed assembly-language-using coder's box of tricks. Nowadays
we are much more sophisticated ;-)
Consider this simple bit of Groovy:
public class Main {
static void main(args) {
new Stuff().doSomething()
}
}
And:
public class Stuff {
def doSomething = { -> println 'Some slow Groovy stuff...' }
}
Running this trivial app gives:
Some slow Groovy stuff...
Let us assume that doSomething() turns out to be glacially slow, and moreover is a hotspot for our application. What's a boy/girl to do?
First, build a nice shiny piece of efficient Java code:
package optimized;
public class Stuff {
public Object doSomething() {
System.out.println ("Something efficient in Java.");
return null;
}
}
(there's nothing special about the package or the Class name, by the way; this just 'felt' like the best way…YMMV)
Now the Groovy Delegating MetaClass magic:
package groovy.runtime.metaclass
public class StuffMetaClass extends groovy.lang.DelegatingMetaClass {
private final def optimized = new optimized.Stuff()
@Override
StuffMetaClass(MetaClass delegate) {
super(delegate);
}
@Override
public Object invokeMethod(Object a_object, String a_methodName, Object[] a_arguments) {
(a_methodName == 'doSomething') ? optimized.doSomething() : super.invokeMethod(a_Object, a_methodName, a_arguments)
}
}
This simple bit of code looks to see if Stuff.doSomething() is being invoked, and if so kicks off to the Java (presumably optimized) version; anything else is allowed to proceed without interference.
Note ye well: this solution is completely non-intrusive: no source had to be altered, nothing needed recompiling.
Run this, and you will see:
Something efficient in Java.
And there you have it.
This is a great way of putting temporary patches in place, or doing hit-and-run tracing of code…
I guess that there is a (not new, but still…) security concern to be aware of here too: lock up your classpath or fall foul of snooping/code injections.
And how's this for a business case: ship a 'slow' application and then dangle the "efficiency upgrade" in front of your users noses…there's no need for fancy patching toolkits, so it costs almost nothing to tell your users: "put
this jar there and hey-presto your new upgrade to SuperApp++ 3000 will be complete" ;-)