Creating self-contained, executable Jars with Gradle and Shadow
July 16th, 2013
Describes how to use Gradle and its Shadow plugin to create a self contained, executable Jar. No more classpath management.
I was privileged and honored this past week to be able to stand amongst the giants of the Groovy and Grails community at Gr8Conf.US. In doing so, I gave a presentation entitled “Application Architecture in Groovy,” wherein I outlined some techniques and practices that I’ve honed over the years in leveraging the features offered by Groovy to develop robust and maintainable application stacks.
Part of that presentation involved demonstrating a somewhat obscure and not-very-well-known feature of Groovy, whereby developers can create a MetaClass implementation that follows the package and class naming convention of groovy.runtime.metaclass.[package].[class]MetaClass, and have that custom implementation act as the concrete metaclass for the provided class type. This exposes a great deal of customization to developers, and using this trick responsibly means that runtime mixins can be modularized in a very clean and effective way. I demonstrated an example of building a simple MetaClass implementation that hooked into the String class’s invokeMethod method, and handled some utility conversions of Strings (string to map, string to json, etc).
But that demonstration was really just the tip of the iceberg for what is possible with customizing the dynamic nature of Groovy. The underlying MetaClass extension system that made my demonstration possible also affords some deeper customizations that we can leverage to keep our code modular, clean, and DRY.
During my presentation, I talked a lot about the idea that when designing a Groovy application, the architect should make use of the fact that Groovy is dynamic, and make it so that some things “just work.” However, one of the principles that I outlined for employing “Groovy magic” was to be sure that the use of magic is very explicit. Leveraging convention-over-configuration principles, which the Groovy ecosystem thrives on, is a great way to make magical shortcuts much more explicit, while still being easy to follow and understand.
It makes sense that we should be able to use the same systems that resolve MetaClass implementations through convention to also resolve runtime method mixins in a similar convention. This shortcut would mean that we can isolate out some of that “Groovy magic,” making it more explicit, while still garnering the benefits of its use. To get to this point, there’s a little bit of plumbing that must happen in advance. To start with, we’ll need to tell the Groovy “system” that our new convention-resolvable-mixin MetaClass implementation is the default MetaClass that we want to be applied to new objects. Luckily, Groovy provides a decent abstraction to the workflow that stands up MetaClass implementations when they’re needed. But before we can begin down that road, it’s important to take a quick detour to understand what Groovy is doing under the hood that allows us to make these time-saving strategies possible.
To start with, as is the case with all scripting languages on the JVM, when a method is called, the invocation is delegated to a call site that does some predeterminations that makes sense of how a method call will be performed. In the case of Groovy, that initially invoked call site makes those determinations based on factors including static method calls, and Groovy or Java object support. After the call site determines *what* the type of object is against which the method is being invoked (also referred to as the ‘receiver’), the method invocation can then be delegated off to another call site for handling.
Once delegated to the appropriate call site, Groovy will then reach out to the MetaClassRegistry to get the right MetaClass for the receiver. The MetaClassRegistry in turn makes a call to a MetaClassCreationHandle that is responsible for determining the proper MetaClass and creating it for that object. Within the MetaClassCreationHandle, the Groovy system reaches out to the convention package to resolve custom MetaClass implementations, but if no custom MetaClass implementation exists for the receiver, then a default MetaClass is chosen and used. Thereafter, the method call is delegated to that MetaClass, which is responsible for either handling the method call itself or choosing to give it back to the receiver for handling.
With that understanding of the method invocation lifecycle, it’s easy to understand that there are checkpoints in this process where we can provide our own influence to stand up a more customizable system. By providing our own implementation of a MetaClassCreationHandle, we can make sure that the default MetaClass implementation that gets chosen for classes is one that is amenable to mixing in methods from a class that is resolvable through convention. In true Groovy fashion, the code required to implement this seemingly complex scenario is surprisingly compact.
The first step is to provide a custom implementation for the MetaClassCreationHandle, so that we can tell it to use our custom MetaClass as the default implementation. In this example, our custom MetaClass is called MixinResolvingMetaClass. It’s important to note that these classes must exist as Java classes, otherwise some funky behavior will occur that will eventually result in a StackOverflowError.
package com.objectpartners.groovy.ext;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassRegistry;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.metaclass.ClosureMetaClass;
class CustomMetaClassCreationHandle extends MetaClassRegistry.MetaClassCreationHandle {
protected MetaClass createNormalMetaClass(Class theClass,MetaClassRegistry registry) {
if (GeneratedClosure.class.isAssignableFrom(theClass)) {
return new ClosureMetaClass(registry,theClass);
} else {
return new MixinResolvingMetaClass(registry, theClass);
}
}
}
We’ll design the MixinResolvingMetaClass to resolve class mixins from the package location and naming convention of groovy.runtime.metaclass.[package].[class]Mixins. Classes that follow this placement and naming rule will have their mixin methods usable throughout the application.
package com.objectpartners.groovy.ext;
// imports...
class MixinResolvingMetaClass extends MetaClassImpl {
static final Map<Class, Class> cachedMixins = new ConcurrentHashMap<>();
MixinResolvingMetaClass(MetaClassRegistry registry, Class theClass) {
super(registry, theClass);
}
// This is the method that gets called when you invoke a method on a class
public Object invokeMethod(Object obj, String name, Object[] args) {
Object mixin = cachedMixins.get(obj.getClass());
if (mixin == null) {
try {
// Resolve the mixin from the convention package and class nomenclature
// In this case, we'll use `groovy.runtime.metaclass.[package].[class]Mixins` to find the mixins for a given class
Class mixinClass = Class.forName("groovy.runtime.metaclass."+obj.getClass().getName()+"Mixins");
cachedMixins.put(obj.getClass(), mixinClass);
mixin = cachedMixins.get(obj.getClass());
} catch (ClassNotFoundException e) {
// There was no mixin class discovered, so kick this call up north for processing
return super.invokeMethod(obj, name, args);
}
}
// Shouldn't happen, but never-say-never :-)
if (mixin == null) throw new RuntimeException("Could not resolve base class for invocation.");
// Invoke the discovered mixin
return handleStaticInvoke((Class)mixin, obj, name, args);
}
private Object handleStaticInvoke(Class mixin, Object obj, String name, Object[] args) {
// Mixin methods are always defined as static methods, so try to discover them here.
Method staticMethod = getStaticMethod(mixin, name, argsWithSelf(obj, args));
// If the mixin didn't have this method don't exist...
if (staticMethod == null) {
// Try to resolve the static method call from the static meta method registry.
// This call is also responsible for resolving receiver method calls that were *actually* static method calls.
MetaMethod metaMethod = getStaticMetaMethod(name, args);
if (metaMethod != null) {
// Invoke the resolved method and return the result.
return metaMethod.invoke(mixin, args);
}
// If we still didn't find a method, throw the expected exception.
throw new MissingMethodExceptionNoStack(name, theClass, args, true);
}
// If the mixin did have this method, then go ahead and invoke it and return the result.
return invokeStaticMethod(mixin, name, argsWithSelf(obj, args));
}
private Object[] argsWithSelf(Object self, Object[] args) {
Object[] selfArgs = new Object[args.length+1];
selfArgs[0] = self;
for (int i=0;i<args.length;i++) {
selfArgs[i+1] = args[i];
}
return selfArgs;
}
private static Object invokeStaticMethod(Class target, String name, Object[] args) {
try {
return getStaticMethod(target, name, args).invoke(target, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static Method getStaticMethod(Class target, String name, Object[] args) {
try {
return target.getMethod(name, MetaClassHelper.castArgumentsToClassArray(args));
} catch (Exception e) {
return null;
}
}
}
For the purposes of this post and example, the mixin classes will be allowed to define mixin methods in the same way that Category classes do. Mixin methods will be resolvable from statically defined methods that take an instance of the receiver as the first vararg.
To get the Groovy system to use our CustomMetaClassCreationHandle, there’s a little bit of plumbing that must be done in advance. The following example shows a mixin class, including package location and class naming convention, as well as the signature required for a mixin method to be resolved.
package groovy.runtime.metaclass.java.lang
import groovy.json.JsonSlurper
class StringMixins {
// Must be static & must take an instance of the receiver as the first vararg
static def toJson(String input) {
new JsonSlurper().parseText(input)
}
}
As you can see from this example, we need to tell Groovy to use our CustomMetaClassCreationHandle before the mixins will be resolvable. In a Grails application, for example, this override can take place in the BootStrap.groovy
class, but may be more appropriately suited to be called from a custom ServletContextListener class that performs this operation when the container gets loaded. Your milage will certainly vary.
The demo code for this post is available on GitHub. Please feel free to post any questions or comments below. I hope that you find useful the concepts and strategies discussed.
Describes how to use Gradle and its Shadow plugin to create a self contained, executable Jar. No more classpath management.
A tutorial with backing github project detailing how to build custom authentication solutions with the Grails Spring Security Core plugin.
Run Quartz jobs to fire only once per cluster, not once per server, while still providing beans from the Spring managed context and using the latest version of Quartz.
Insert bio here