Using Meta-annotations to DRY Your Groovy
When you’re using grails every day you forget how much the grails conventions can keep your code DRY. I love dropwizard and I don’t mind hibernate (too much) but adding the same 3 or 4 annotations on every class gets old. Groovy 2.1.0+ has a feature to help with this called meta-annotations or annotation collections. This feature replaces a single annotation with a collection of them at compile time. Take a hibernate entity like this one:
1 2 3 4
Every one of our entities has these annotations on it. You can define a meta-annotation which includes all of those annotations.
1 2 3 4 5
Then the user entity definition is reduced:
name attribute is then passed to every annotation in the
@JPAEntity meta-annotation which has that property. In this case it is only the
This technique not only lets you reduce some repeated code, but makes it easy to apply consistant changes to every class. If you decide you don’t want to use static compilation anymore, just remove it from the meta-annotation. If you want to add a canonical constructor to every entity, just add
You can do more complicated things by defining your own collection processor. Jersey resources are annotation heavy as well and it would be nice to reduce that. Here’s a basic resource definition for a user resource:
1 2 3 4 5
I want to define resources like this:
Here’s the meta-annotation:
1 2 3 4 5
I also wanted to add an
@Produces annotation but that requires an enum passed to the value attribute and you can’t do that in an annotation collection. So I defined an annotation collection processor to customize the path annotation and add the produces annotation at compile time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
This one requires a little more explanation since it’s using an AST to rewrite the annotations on the class at compile time. The basic idea is to get build a new list of annotations and then return them from the
First references are grabbed to the compile static and slf4j annotation nodes which aren’t going to be changed at all.
1 2 3 4
The resource annotation
@DropwizardResource('/users') provides a path which needs to be set on the value attribute of the
@Path annotation. First the value attribute of the meta-annotation is found. If the usage doens’t include a path, then it is set to an empty string expression. Then the value of the
@Path annotation is set with the path expression. Finally the value from the usage of @DropwizardResource is removed so that it won’t be reused by any other annotation.
This part is slightly trickier. A new annotation needs be constructed at compile time. This is necessary because the value passed to
@Produces is an enum and meta-annotations don’t support using enum values. ASTs make this reasonably easy but it is a little mind bending to understand the first few times.
Finally the new list of annotations is returned and compilation continues.
Meta-annotations are a great way to reduce annotation duplication and make it a lot easier to make application-wide changes in a single place. They are also a good way to learn the basics of AST transformations.