Core Java

Processing Java Annotations Using Reflection

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;

/**
 * Annotation created for doing it like this.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoItLikeThis {

	/**
	 * @return - The description.
	 */
	String description() default "";

	/**
	 * @return - The action.
	 */
	String action() default "";

	/**
	 * @return - Should we be doing it like this.
	 */
	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;

/**
 * Annotation created for doing it like that
 * instead of like this.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoItLikeThat {

	/**
	 * @return - Should we be doing it like that.
	 */
	boolean shouldDoItLikeThat();

	/**
	 * @return - List of user roles that can do it like that.
	 */
	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;

/**
 * When you can't do it like this or do it like that,
 * do it with a whiffle ball bat.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoItWithAWhiffleBallBat {

	/**
	 * @return - Should we be doing it with a whiffle ball bat.
	 */
	boolean shouldDoItWithAWhiffleBallBat() default false;

	/**
	 * @return - Sweet, which type of whiffle ball bat?
	 */
	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;

	/* setters and getters removed for brevity */

	@DoItWithAWhiffleBallBat(batType = WhiffleBallBat.BLACK_PLASTIC, shouldDoItWithAWhiffleBallBat = true)
	public void doWhateverItIs() {
		// method implementation
	}

	public void verifyIt() {
		// method implementation
	}

}

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;

	/* setters and getters removed for brevity */

	@DoItWithAWhiffleBallBat(shouldDoItWithAWhiffleBallBat = true)
	public void doWhateverItIs() {
		// method implementation
	}

	public void verifyIt() {
		// method implementation
	}

}

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)) {
		// process the annotation, "ac" being the instance of the object we are inspecting

	}

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)) {
			// process the annotation, "ac" being the instance of the object we are inspecting
			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 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		AnnotatedClassProcessor processor = new AnnotatedClassProcessor();
		processor.processClass(new AnnotatedOne());
		processor.processClass(new AnnotatedTwo());

	}

}
Reference: Processing Java Annotations Using Reflection from our JCG partner Jonny Hackett at the Keyhole Software blog.

Keyhole Software

Keyhole is a midwest-based consulting firm with a tight-knit technical team. We work primarily with Java, JavaScript and .NET technologies, specializing in application development. We love the challenge that comes in consulting and blog often regarding some of the technical situations and technologies we face.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button