Weapons for Boilerplate Destruction [Pt.2] — Annotation Processor

František Gažo
INLOOPX
Published in
5 min readDec 21, 2016

If you don’t know what an annotation processor is, then I suggest you read my previous post before you start digesting this one.

In this post you’ll learn how to create your own annotation processor and also how to test it. Interested? Read on :)

I’ll be using Android Studio because this post targets android developers.

What will we create? An annotation processor that will add logging to annotated methods. I know that there are some libraries that do this very well, but you will learn some basics on this project and that should be enough.

All the source code from this post can be found on github.

Project Setup

The basic project contains 3 modules:

Project Setup
  1. app — Application module that can be used for manual testing.
  2. annotations Java Library module that will contain your custom annotations. This is only necessary if you want to use your own annotations as I’ll do in this example.
  3. processor Java Library module that will contain your annotation processor.

Annotations Module

Let’s start by setting up the annotations module.

Your gradle.build file should look like this (Make sure sourceCompatibility and targetCompatibility are set to Java 7. No additional dependencies are needed right now):

build.gradle

Now create a custom annotation:

Logged.java

As you can see @Logged is annotated with 2 annotations — each has a specific role. Simply put: @Target defines where it can be used (@Logged can be used only on methods) and @Retention defines the moment till it is kept in the code (@Logged is removed from the code before .class files are created). See docs for more information or leave a comment below.

That’s all for this module. The whole structure should look like this:

Annotations Module Structure

Annotation Processor Module

The gradle.build file should look like this (Again make sure sourceCompatibility and targetCompatibility are set to Java 7):

You need to add the annotations module as dependency and also 2 more libraries:

  1. AutoService is Java annotation processors for generating META-INF metadata
  2. JavaPoet is Java API for generating .java source files.

Ok, everything is ready — Let’s create the Annotation Processor finally.

This is the core of an annotation processor:

As you can see we annotated the class above with @AutoService in order to generate the required metadata.

Then in init(ProcessingEnvironment) we initialized the fields with Filer (for creating files) and Messager (for printing messages to console).

It’s good practice to return SourceVersion.latestSupported() in getSupportedSourceVersion().

From getSupportedAnnotationTypes() you need to return a Set of all annotation names you want to be notified about (right now we are going to register only for “eu.f3rog.log.Logged”). If you want, you can register for all annotations by simply using “*”.

And process(Set<? extends TypeElement>, RoundEnvironment) contains the logic you want to do when an annotation you registered for is found. This method can be called more than once, depending on whether some annotation processors created a class containing annotation you are registered for (RoundEnvironment has method processingOver() that you can use to determine if the current call is the last). It is important to return false (if you return true other processors won’t be notified about these annotations).

In this example I’ll put the code inside one class for simplicity, but be a good citizen and structure your code when creating your own processor ;)

What exactly will this processor do? Let’s write it out:

  1. Get all methods that are annotated with @Logged.
  2. Divide them into groups based on the class that contains them.
  3. For each class that contains annotated methods, generate new class that will contain methods with logging — one for each annotated method.

Finding and dividing methods into groups is a pretty straight forward task:

Creating a new class requires a little bit more code, but thanks to JavaPoet and it’s nice API it’s really simple and the only thing you need to be concerned about is how to get the information you need for the generated code. I won’t go into detail of how to use JavaPoet because they have a very helpful README on their github page.

What I would like to mention is that an annotation processor sees everything as an Element. You can ask for all elements that have a specific annotation. Depending on the annotation’s @Target, you can get TypeElement (class, interface), VariableElement (field, parameter) or ExecutableElement (method). From there you can access it’s name, type, enclosed elements, element that encloses this one, etc.

Classes that are Android specific can be referenced via ClassName.get(package, className) (as I did in the example) or you can use this dependency:

compile 'com.google.android:android:4.1.1.4'

App Module

Now it’ time to try it out.

Add these dependencies to your app module:

compile project(':annotations')
apt project(':processor')

In order to support apt you’re going to need a gradle plugin. So Add this classpath

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

and apply it in your build.gradle.

apply plugin: 'com.neenbedankt.android-apt'

Now you can use @Logged in your code — e.g. like this:

And when you run your app, you’ll see this log:

D/eu.f3rog.apt.MainActivity.doSomething: called on eu.f3rog.apt.MainActivity@418e0bb0 with text:Hello, num:3

Hurray, it works! :). But let’s admit it’s not very user friendly, right?

In the next post I’ll show you how to get rid of the need to manually add the logging call.

Originally I wanted to show you a way to unit test the annotation processor but that would make this post too long, so I’ll make a separate post for that and add the link here directly when it’s ready.

If you have any questions or want me to write a post on something specific, please leave a comment!

See you later ;)

--

--