In my previous article covering Java Annotations, I outlined a recent use case and provided you with some examples of custom annotations and how they might be used.
In this article, I’m going to take that a step further and give you a few examples of custom annotations and how you would process these custom annotations using the Java Reflection API. Once you have gone through this tutorial, you should come away with a better understanding of the simplicity and flexibility that custom annotations can provide. So let’s dig into the code!
Custom Annotation Listings
I have created three different annotations for the example code today which are the DoItLikeThis
, DoItLikeThat
and DoItWithAWhiffleBallBat
annotations. Each annotation targets a different element type and has slightly different properties so that I can show you how to look for and process them accordingly.
DoItLikeThis Annotation
The DoItLikeThis
annotation is targeted for the ElementType TYPE
, which makes it only available for Java types. This annotation has the three optional elements description, action, and a boolean field shouldDoItLikeThis
. If you don’t provide any values for these elements when using this annotation, they will default to the values specified.
package com.keyhole.jonny.blog.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoItLikeThis {
String description() default "";
String action() default "";
boolean shouldDoItLikeThis() default false;
}
DoItLikeThat Annotation
The DoItLikeThat
annotation is an annotation that is targeted for Java fields only. This annotation also has a similar boolean element named shouldDoItLikeThat
, which doesn’t specify a default value and is therefore a required element when using the annotation. The annotation also contains an element defined as a String
array which will contain a list of user roles that should be checked.
package com.keyhole.jonny.blog.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoItLikeThat {
boolean shouldDoItLikeThat();
String[] roles() default{};
}
DoItWithAWhiffleBallBat Annotation
The DoItWithAWhiffleBallBat
annotation is targeted for use with methods only and similar to the other annotations. It also has a similar boolean element, this one is named shouldDoItWithAWhiffleBallBat
. There is also another element defined which makes use of a WhiffleBallBat enum
that defines the different type of whiffle ball bats that are available for use, defaulting to the classic yellow classic whiffle ball bat.
package com.keyhole.jonny.blog.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoItWithAWhiffleBallBat {
boolean shouldDoItWithAWhiffleBallBat() default false;
WhiffleBallBat batType() default WhiffleBallBat.YELLOW_PLASTIC;
}
Annotated Classes
Now that we have our annotations defined for our example, we need a couple of classes to annotate. Each class provides example uses of the annotations with elements specified as well as relying on the default values. There are also additional fields and methods included that are not annotated and therefore should not be processed by the annotation processor. Here is the source code for the two example classes:
AnnotatedOne Class
package com.keyhole.jonny.blog.annotations;
import java.util.Date;
@DoItLikeThis
public class AnnotatedOne implements AnnotatedClass {
@DoItLikeThat(shouldDoItLikeThat = false)
private String field1;
@DoItLikeThat(shouldDoItLikeThat = true, roles = { "admin", "root" })
private String field2;
private String field3;
private Date dateDoneLikeThis;
@DoItWithAWhiffleBallBat
(batType = WhiffleBallBat.BLACK_PLASTIC, shouldDoItWithAWhiffleBallBat = true)
public void doWhateverItIs() {
}
public void verifyIt() {
}
}
AnnotatedTwo Class
package com.keyhole.jonny.blog.annotations;
import java.util.Date;
@DoItLikeThis(action = "PROCESS", shouldDoItLikeThis = true,
description = "Class used for annotation example.")
public class AnnotatedTwo implements AnnotatedClass {
@DoItLikeThat(shouldDoItLikeThat = true)
private String field1;
@DoItLikeThat(shouldDoItLikeThat = true, roles = { "web", "client" })
private String field2;
private String field3;
private Date dateDoneLikeThis;
@DoItWithAWhiffleBallBat(shouldDoItWithAWhiffleBallBat = true)
public void doWhateverItIs() {
}
public void verifyIt() {
}
}
Processing Annotations
Processing annotations using reflections is actually quite simple. For each of the element types that you can create for and apply annotations to, there are methods on those elements for working with annotations. The first thing you will need to do is inspect the element to determine if there are any annotations or check to see if a particular annotation exists for the element.
Each of the element types Class
, Field
, and Method
all implement the interface AnnotatedElement
, which has the following methods defined:
getAnnotations()
– Returns all annotations present on this element, which includes any that are inherited. getDeclaredAnnotations()
– Returns only the annotations directly present on this element. getAnnotation(Class<A> annotationClass)
– Returns the element’s annotation for the specified annotation type, if not found this returns null
. isAnnotation()
– Returns true
if the element being inspected is an annotation. isAnnotationPresent(Class<? Extends Annotation> annotationClass)
– Returns true
if the annotation specified exists on the element being checked.
When processing our annotations, the first thing we will want to do is check to see if the annotation is present. To do this, we’ll wrap our annotation processing with the following check:
if (ac.getClass().isAnnotationPresent(DoItLikeThis.class)) {
}
Once we have found the annotation we are looking for, we will grab that annotation and do whatever processing we want to do for that annotation. At this point, we’ll have access to the annotations’ elements and their values. Notice there are not any getters or setters for accessing the elements of the annotation.
DoItLikeThis anno = ac.getClass().getAnnotation(DoItLikeThis.class);
System.out.println("Action: " + anno.action());
System.out.println("Description: " + anno.description());
System.out.println("DoItLikeThis:" + anno.shouldDoItLikeThis());
For fields and methods, checking for present annotations will be slightly different. For these types of elements, we’ll need to loop through all of the fields or methods to determine if the annotation exists on the element. You will need to get all of the fields or methods from the Class
, loop through the Field
or Method
array, and then determine if the annotation is present on the element. That should look something like this:
Field[] fields = ac.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(DoItLikeThat.class)) {
DoItLikeThat fAnno = field.getAnnotation(DoItLikeThat.class);
System.out.println("Field: " + field.getName());
System.out.println("DoItLikeThat:" + fAnno.shouldDoItLikeThat());
for (String role : fAnno.roles()) {
System.out.println("Role: " + role);
}
}
}
Conclusion
As you can see, creating your own annotations and processing them is fairly simple. In the examples I have provided, we are simply outputting the values of the elements to the console or log. Hopefully, you can see the potential use of these and might actually consider creating your own in the future. Some of the best uses I’ve seen for annotations are where they replace some configuration code or common code that gets used often, such as validating the value of a field or mapping a business object to a web form.
And finally, here is the full source code along with a simple Java main
class to execute the code:
AnnotatedClassProcessor
package com.keyhole.jonny.blog.annotations;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class AnnotatedClassProcessor {
public void processClass(AnnotatedClass ac) {
System.out.println("------Class Processing Begin---------");
System.out.println("Class: " + ac.getClass().getName());
if (ac.getClass().isAnnotationPresent(DoItLikeThis.class)) {
DoItLikeThis anno = ac.getClass().getAnnotation(DoItLikeThis.class);
System.out.println("Action: " + anno.action());
System.out.println("Description: " + anno.description());
System.out.println("DoItLikeThis:" + anno.shouldDoItLikeThis());
System.out.println("------Field Processing---------");
Field[] fields = ac.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(DoItLikeThat.class)) {
DoItLikeThat fAnno = field.getAnnotation(DoItLikeThat.class);
System.out.println("Field: " + field.getName());
System.out.println("DoItLikeThat:" + fAnno.shouldDoItLikeThat());
for (String role : fAnno.roles()) {
System.out.println("Role: " + role);
}
}
}
System.out.println("------Method Processing---------");
Method[] methods = ac.getClass().getMethods();
for (Method method : methods) {
if ( method.isAnnotationPresent(DoItWithAWhiffleBallBat.class)) {
DoItWithAWhiffleBallBat mAnno = method.getAnnotation(DoItWithAWhiffleBallBat.class);
System.out.println("Use WhiffleBallBat? " + mAnno.shouldDoItWithAWhiffleBallBat());
System.out.println("Which WhiffleBallBat? " + mAnno.batType());
}
}
}
System.out.println("------Class Processing End---------");
}
}
RunProcessor
package com.keyhole.jonny.blog.annotations;
public class RunProcessor {
public static void main(String[] args) {
AnnotatedClassProcessor processor = new AnnotatedClassProcessor();
processor.processClass(new AnnotatedOne());
processor.processClass(new AnnotatedTwo());
}
}
— Jonny Hackett, asktheteam@keyholesoftware.com