Customizing MOP in Groovy
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.
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.
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.
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.