18.6 Adding Methods at Compile Time

Grails 3.0 makes it easy to add new traits to existing artefact types from a plugin. For example say you wanted to add methods for manipulating dates to controllers. This can be done by defining a trait in src/main/groovy:

  1. package myplugin
  2. @Enhances("Controller")
  3. trait DateTrait {
  4. Date currentDate() {
  5. return new Date()
  6. }
  7. }

The @Enhances annotation defines the types of artefacts that the trait should be applied to.

As an alternative to using the @Enhances annotation above, you can implement a TraitInjector to tell Grails which artefacts you want to inject the trait into at compile time:

  1. package myplugin
  2. @CompileStatic
  3. class ControllerTraitInjector implements TraitInjector {
  4. @Override
  5. Class getTrait() {
  6. SomeTrait
  7. }
  8. @Override
  9. String[] getArtefactTypes() {
  10. ['Controller'] as String[]
  11. }
  12. }

The above TraitInjector will add the SomeTrait to all controllers. The getArtefactTypes method defines the types of artefacts that the trait should be applied to.

Applying traits conditionally

A TraitInjector implementation can also implement the SupportsClassNode interface to apply traits to only those artefacts which satisfy a custom requirement.For example, if a trait should only be applied if the target artefact class has a specific annotation, it can be done as below

  1. package myplugin
  2. @CompileStatic
  3. class AnnotationBasedTraitInjector implements TraitInjector, SupportsClassNode {
  4. @Override
  5. Class getTrait() {
  6. SomeTrait
  7. }
  8. @Override
  9. String[] getArtefactTypes() {
  10. ['Controller'] as String[]
  11. }
  12. boolean supports(ClassNode classNode) {
  13. return GrailsASTUtils.hasAnnotation(classNode, SomeAnnotation)
  14. }
  15. }

Above TraitInjector will add the SomeTrait to only those controllers which has the SomeAnnotation declared.

The framework discovers trait injectors by way of a META-INF/grails.factories descriptor that is in the .jar file. This descriptor is automatically generated. The descriptor generated for the code shown above would look like this:

  1. #Grails Factories File
  2. grails.compiler.traits.TraitInjector=
  3. myplugin.ControllerTraitInjector,myplugin.DateTraitTraitInjector
Due to formatting issues, above code snippet includes a line break after equal sign.

That file is generated automatically and added to the .jar file at build time. If for any reason the application defines its own grails.factories file at src/main/resources/META-INF/grails.factories, it is important that the trait injectors be explicitly defined in that file. The auto-generated metadata is only reliable if the application does not define its own src/main/resources/META-INF/grails.factores file.