Groovy meta-programming and Grails Artefact-API
The list of transient property names in Grails domain-classes can get quite long in bigger setups. Also often multiple domain-classes share the same transient references so there are many places to keep synced when refactoring or extending.
An easy alternative is to use Groovy’s meta-programming capabilities to define shared transient Grails references. For example you can easily add the grailsApplication
to the metaclass of domain classes. Wit that you can access the config from grailsApplication.config
, or Spring beans via grailsApplication.mainContext.foo
.
All there is to be done is letting spring dependency-inject the reference of the GrailsApplication into Bootstrap.groovy and to use that reference when defining closures on the meta-class:
[groovy title="Bootstrap.groovy" highlight="6,7"] @Autowired GrailsApplication grailsApplication for (GrailsClass dc in grailsApplication.getArtefacts("Domain")) { // define transient references dc.clazz.metaClass.getGrailsApplication = { -> grailsApplication } dc.clazz.metaClass.setGrailsApplication = { -> grailsApplication } } [/groovy]
You can also that approach to overwrite already exisiting methods on domain-classes:
[groovy title="Bootstrap.groovy" highlight="1"] GrailsClass grailsClassDomain = grailsApplication.getArtefact("Domain", "fully.qualified.domainClassName") grailsClassDomain.clazz.metaClass.list = {super.list()} [/groovy]
Grails Artefact-API
We see that Grails Artefact-API is used to get Grails domain-objects as a GrailsClass. There are various artefacts known to a Grails application. These artefacts get registered to the GrailsApplication via an ArtefactHandler. Subclasses of ArtefactHandlerAdapter respectivly implementations of the ArtefactHandler interface currently define the types of artefacts in a Grails application:
- JobArtefactHandler with artefact-typename: “Job”
- FiltersConfigArtefactHandler with artefact-typename: “FiltersConfig”
- CodecArtefactHandler with artefact-typename: “Codec”
- TagLibArtefactHandler with artefact-typename: “TagLib”
- ServiceArtefactHandler with artefact-typename: “Service”
- ControllerArtefactHandler with artefact-typename: “Controller”
- BootstrapArtefactHandler with artefact-typename: “Bootstrap”
- AnnotationDomainClassArtefactHandler with artefact-typename: “Domain”
- ResourceMapperArtefactHandler with artefact-typename: “ResourceMapper”
- CacheConfigArtefactHandler with artefact-typename: “CacheConfig”
- UrlMappingsArtefactHandler with artefact-typename: “UrlMappings”
- ResourcesArtefactHandler with artefact-typename: “Resources”
[groovy title="getting all ArtefactHandlers"] ArtefactHandlerer[] ahs = grailsApplication.getArtefactHandlers() [/groovy]
Groovy’s metaobject protocol and meta-programming
The domain-enrichment shown above is done the same way Grails is already providing it’s dynamic-methods to us.
We use a GrailsClass and call the getMetaClass() method in order to get Groovy’s ExpandoMetaClass which allows us to add methods to already instantiated objects. This kind of meta-programming is possible because Groovy is implementing a metaobject protocol. A metaobject protocol (MOP) is an open and extensible interpreter of program-semantics. The MOP can expose some or all internal structure of the interpreter to the programmer and are implemented as object-oriented programs where all objects are metaobjects.
Groovy’s metaobject protocol defines an interface that defines the API usable by programmers who can alter exisiting object-behavior by extending parts of the MOP. The reference implementation is declared with the initially mentioned MetaClass
interface.