Java Annotation Processors – An introduction
This is the introduction part of a blogpost series, in which we will take a look at the basic functionality of annotation processors using a small example. The code used in this article can be viewed at https://github.com/cloudogu/annotation-processors/. During the build, annotation processors are called by the compiler (javac) when one of the configured annotations has been found. In this context, the annotation processor is able to determine which annotations it wishes to be notified of. This can include single, multiple, or all annotations. If the compiler finds an annotation, it checks whether a processor has been registered for it. If this is the case, it is executed. At this point, the annotation processor can perform its task and determine whether additional processors should be called for the annotation found. In the first section of this article, we will create a simple annotation processor that issues a log message during compilation. To begin with, we will only use this annotation processor with the help of the Java compiler. Then, we will see how our simple annotation processor can be used with a build tool, such as Maven.
A simple annotation processor
Annotation processors must implement the javax.annotation.processing.Processor
interface; in most cases it is recommended to extend the javax.annotation.processing.AbstractProcessor
class, as it contains useful auxiliary methods. Our example annotation processor is supposed to issue a notification when a certain annotation is found. To do so, we must first create our annotation (com/cloudogu/blog/annotationprocessor/log/Log.java)
:
@Target({ElementType.TYPE})
public @interface Log {}
The Target annotation with the parameter ElementType.TYPE
from our Log annotation specifies that we can use @Log with all Java types (classes, interfaces, or enums). Whenever Javac finds this annotation, we want a message to be displayed on the console that shows us which class the annotation uses. The annotation processor (com/cloudogu/blog/annotationprocessor/log/LogProcessor.java/)
for the Log
annotation:
@SupportedAnnotationTypes("com.cloudogu.blog.annotationprocessor.log.Log")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class LogProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for ( TypeElement annotation : annotations ) {
for ( Element element : roundEnv.getElementsAnnotatedWith(annotation) ) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "found @Log at " + element);
}
}
return true;
}
}
The SupportedAnnotationTypes
annotation determines the annotations for which our processor is called. It is also possible to use “*”; in this case, the processor is called for every annotation that is found. The SupportedSourceVersion
annotation shows the latest Java version the annotation processor supports. If the annotation processor is used with a newer version of Java, a warning will be shown informing the user that the processor does not support this version of Java.
We then have to implement the process
method of the AbstractProcessor. Two values are passed to the method:
- A set of
java.lang.model.element.TypeElement
, which contains all annotations found. javax.annotation.processing.RoundEnvironment
– with this object, it is possible to inspect the annotated elements that have been found.
If the process
method returns true
, no further annotation processors will be called for the annotation that was found. If it returns false, additional annotation processors can be notified for this annotation. By extending the AbstractProcessor
, you can also access the processingEnv
variable of the type javax.annotation.processing.ProcessingEnvironment
. ProcessingEnvironment
enables access to the compiler environment, for example to abort a build process or display a message on the console.
In our example:
- We first iterate using the set of annotations found:
for ( TypeElement annotation : annotations ) {
- Using RoundEnvironment, we then search for the elements that have been annotated with this annotation:
for ( Element element : roundEnv.getElementsAnnotatedWith(annotation) ) {
- Next, a log of all elements found with ProcessingEnvironment is displayed on the console:
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "found @Log at " + element);
Registration
In order for the compiler to be able to find the annotation processor, it must be registered. This is done using the ServiceLoader
, introduced with Java 6. To do this, we have to create a file under META-INF/services/javax.annotation.processing.Processor
, which contains the full name of our annotation processor. In our example this would be: com.cloudogu.blog.annotationprocessor.log.LogProcessor
Usage
To be able to use the processor, we need a class that is annotated with @Log, for example (com/cloudogu/blog/annotationprocessor/sample/Hello.java)
:
@Log
public class Hello {
public static void main(String[] args) {
System.out.println("Hello");
}
}
After creating the example class, we can compile and test our annotation processor. First, we compile the annotation and the processor:
javac –cp . -proc:none com/cloudogu/blog/annotationprocessor/log/*.java
The parameter -proc:none
deactivates all of the annotation processors. This is important, as the compiler already finds our registry (the file saved under META-INF/services), but our processor is not yet compiled. This would lead to an error. Now, we can compile our test class and thus test our annotation processor:
javac –cp . com/cloudogu/blog/annotationprocessor/sample/*.java
The compiler should now call the annotation processor and we should see the following line on the console:
Note: found @Log at com.cloudogu.blog.annotationprocessor.sample.Hello
Annotation processors can also be used with standard build tools and IDEs. In order to use annotation processors with Maven, for example, the build must be divided into separate modules. This separation must take place so that Maven can compile the annotation processor independently and before its use. Our example with Maven can be viewed at the following URL: https://github.com/cloudogu/annotation-processors/tree/master/part-1-maven . Here, it is important to remember that the maven-compiler-plugin
must be configured in both modules. Since compiler warnings are to be issued in the sample module, showWarnings
must be set to true
, and in the log
module, the compiler
must be run with -proc:none
, as described above (compilerArgs)
. If mvn clean install
is now run in the parent
module, the expected message will appear in the output:
[INFO] found @Log at com.cloudogu.blog.annotationprocessor.sample.Hello
Visit our community platform to share your ideas with us, download resources and access our trainings.
Join us nowConclusion
In this blog post we have learned how to write, register and use a simple Annotation Processor. We have also highlighted the possibilities of Annotation Processors. In the next part we will look at the creation of configurations and into the documentation from annotations in more detail. The third part will be about the actual generation of code. So, stay tuned!
This article is part 1 of the series „Java Annotation Processing“.
Read all articles now: