An introductory guide to annotations and annotation processors

@MyAnnotation public class Foo {}
List of marker interfaces in Java
public class Foo implements MarkerInterface {} // 1@MyAnnotation
public class Foo {} // 2
  1. Marker interface
  2. Annotation equivalent to the marker inteface
/**
* Blah blah JavaDoc.
*
* @deprecated As of JDK version 1.1,
*/
public class DeprecatedApi {}
/**
* Blah blah JavaDoc.
*/
@Deprecated
public class DeprecatedApi {}
  • forRemoval (of type boolean): indicates whether the annotated element is subject to removal in a future version
  • since (of type String): returns the version in which the annotated element became deprecated
@Deprecated(since="1.2", forRemoval=true)
public abstract class IdentityScope extends Identity {

Creating an annotation

public @interface MyAnnotation {}
  1. A target: this defines where the annotation can be set
  2. A retention: this describes up to which step in the compilation process the annotation will be available
@Target(ElementType.ANNOTATION_TYPE)      // 1
@interface Foo {}
@Target(ElementType.ANNOTATION_TYPE) // 2
@interface Bar {}
@Foo
@Bar
@interface Baz {}
  1. This is the required @Target, it will be explained further down
  2. Something annotated with @Baz is transitively annotated with both @Foo and @Bar
@Retention(RetentionPolicy.RUNTIME)    // 2
@Target(ElementType.ANNOTATION_TYPE) // 1
public @interface Target {
ElementType[] value();
}
@Retention(RetentionPolicy.RUNTIME) // 2
@Target(ElementType.ANNOTATION_TYPE) // 1
public @interface Retention {
RetentionPolicy value();
}
  1. The @Target annotations tells on which element the annotation can be set:
  • On a type e.g. a class or an interface
  • On another annotation
  • On a field
  • On a method
  • On a constructor
  • On a method parameter
  • On a local variable
  • On a module
  • etc.
  • Only the source code
  • In the class file
  • At runtime
java.lang.annotation package class diagram

Annotation parameters

  • Any primitive type e.g. int, long, etc.
  • String
  • Class<T>
  • Any enum type
  • Another annotation type
  • Any array of the above
@Target(ElementType.CLASS)
@interface Foo {
int bar();
Class<? extends Collection> baz() default List.class;
String[] qux();
}
@Foo(bar = 1, qux = { "a", "b", "c" })
class MyClass {}
@Target(ElementType.CLASS)
@interface Foo {
int value();
}
@Foo(1)
class MyClass {}

Handling annotations at runtime: reflection

var session = request.getHttpSession();
var object = session.getAttribute("objet"); // 1
var clazz = object.getClass(); // 2
var methods = clazz.getMethods(); // 3
for (var method : methods) {
if (method.getParameterCount() == 0) { // 4
method.invoke(foo); // 5
}
}
  1. Get an object stored in the session
  2. Get the runtime class of the object
  3. Get all public methods available on the object
  4. If the method has no parameter
  5. Call the method

Handling annotations at compile-time: annotation processors

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessors>
<annotationProcessor>
ch.frankel.blog.SampleProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
</plugins>
</build>
Java annotation processor class diagram
@SupportedAnnotationTypes("ch.frankel.blog.*")                  // 1          
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SampleProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,// 2
RoundEnvironment env) {
annotations.forEach(annotation -> { // 3
Set<? extends Element> elements =
env.getElementsAnnotatedWith(annotation); // 4
elements.stream()
.filter(TypeElement.class::isInstance) // 5
.map(TypeElement.class::cast) // 6
.map(TypeElement::getQualifiedName) // 7
.map(name -> "Class " + name + " is annotated with " + annotation.getQualifiedName())
.forEach(System.out::println);
});
return true;
}
}
  1. Processor will be called for every annotation that belongs to the ch.frankel.blog package
  2. process() is the main method to override
  3. The loop will be called for each annotation
  4. The annotation is not as interesting as the element annotated with it. This is the way to get the annotated element.
  5. Depending on which element is annotated, it needs to be cast to the correct Element subinterface. Here, only classes can be annotated, hence, the variable needs to be tested to check whether it’s assignable TypeElement to access its additional attributes further down the operation chain
  6. We want the qualified name of the class the annotation is set on, so it’s necessary to cast it to the type that makes this specific attribute accessible
  7. Get the qualified name from TypeElement

Conclusion

--

--

--

Dev Advocate for Apache APISIX. Former developer and architect. Still teaching, learning and blogging.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Weblog Analytics on Apache Hadoop™

💡extra_fields in Django

Tips: Go Unit Testing Performance & How to Debug

How to use Linked lists in C

Why Python is a Supreme Startup Option in 2021

“Scope” in a nutshell

How To be Good at Python Coding?

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nicolas Fränkel

Nicolas Fränkel

Dev Advocate for Apache APISIX. Former developer and architect. Still teaching, learning and blogging.

More from Medium

Java Streams — A quick primer and application

JDK, JRE, and JVM

Design Patterns —Builder Pattern

Is Java dead yet? Key figures about Java usage in 2022