So far, all the discussions on annotations have been around looking at them visuallyeither in source code or in Javadoc. However, there are enough code introspection tools these days that it's worth talking about using reflection to determine what annotations a class (or field, or method) has. The java.lang.reflect package has several additions that make this a piece of cake.
The easiest way to check for an annotation is by using the isAnnotationPresent( ) method. This lets you specify the annotation to check for, and get a true/false result:
public void testAnnotationPresent(PrintStream out) throws IOException { Class c = Super.class; boolean inProgress = c.isAnnotationPresent(InProgress.class); if (inProgress) { out.println("Super is In Progress"); } else { out.println("Super is not In Progress"); } }
NOTE
This code is in the ReflectionTester class.
Running this code gives you the following output:
run-ch06: [echo] Running Chapter 6 examples from Java Tiger: A Developer's Notebook [echo] Running ReflectionTester... [java] Super is In Progress
Additionally, this approach lets you take advantage of the Inherited annotation, described in Setting Up Inheritance in Annotations:
public void testInheritedAnnotation(PrintStream out) throws IOException { Class c = Sub.class; boolean inProgress = c.isAnnotationPresent(InProgress.class); if (inProgress) { out.println("Sub is In Progress"); } else { out.println("Sub is not In Progress"); } }
NOTE
This assumes you follow the steps in "Setting Up Inheritance in Annotations" and mark "Super" as being in progress.
Remember that although Sub is not marked as in progress, it inherits from Super, which is in progress. Additionally, the InProgress annotation was marked as Inherited, so the in-progress indicator should be passed on to subclasses. Running this new method shows that this, indeed, works:
run-ch06: [echo] Running Chapter 6 examples from Java Tiger: A Developer's Notebook [echo] Running VarargsTester... [java] Super is In Progress [java] Sub is In Progress
Although this is not picked up in the Javadoc, it certainly appears in your reflection-based code.
If you're not checking for a marker interface, though, you may have to go beyond isAnnotationPresent( )especially if you need to get values from the annotation. Here's a simple example:
NOTE
Reflection on annotations only works for annotation types that have Runtime retention.
public void testGetAnnotation(PrintStream out) throws IOException, NoSuchMethodException { Class c = AnnotationTester.class; MethodElement element = c.getMethod("calculateInterest", float.class, float.class); GroupTODO groupTodo = element.getAnnotation(GroupTODO.class); String assignedTo = groupTodo.assignedTo( ); out.println("TODO Item on Annotation Tester is assigned to: '" + assignedTo + "'"); }
Once the method in question is located (in this case, calculateInterest( ) on the AnnotationTester class), that method can be queried for a specific annotation. In this case, the code locates the GroupTODO annotation, and grabs the value of assignedTo. The output of this method is shown here:
run-ch06: [echo] Running Chapter 6 examples from Java Tiger: A Developer's Notebook [echo] Running ReflectionTester... [java] Super is In Progress [java] Sub is In Progress [java] TODO Item on Annotation Tester is assigned to: 'Brett McLaughlin'
To use this code, you obviously have to know exactly what you're looking forand that's one of the few drawbacks of getAnnotation( ).
Finally, you can use getAnnotations( ) if you're trying to locate all annotations for a program element, or if you need to iterate through all annotations looking for a specific one. For example, here's a simple utility method that prints out all annotations for a supplied element:
NOTE
The for/in loop is detailed in Chapter 7, and printf( ) and other new formatting methods are covered in Chapter 9.
public void printAnnotations(AnnotatedElement e, PrintStream out) throws IOException { out.printf("Printing annotations for '%s'%n%n", e.toString( )); Annotation[] annotations = e.getAnnotations( ); for (Annotation a : annotations) { out.printf(" * Annotation '%s' found%n", a.annotationType( ).getName( )); } }
If you supplied this method the calculateInterest( ) method from AnnotationTester, you'd get the following output:
run-ch06: [echo] Running Chapter 6 examples from Java Tiger: A Developer's Notebook [echo] Running ReflectionTester... [java] Super is In Progress [java] Sub is In Progress [java] TODO Item on Annotation Tester is assigned to: 'Brett McLaughlin' [java] Printing annotations for 'public void com.oreilly.tiger.ch06. Annotat ionTester.calculateInterest(float,float)' [java] * Annotation 'com.oreilly.tiger.ch06.InProgress' found [java] * Annotation 'com.oreilly.tiger.ch06.GroupTODO' found
This code is really pretty straightforward, so I'll leave it to you to work through the details. Example 6-15 is the complete code listing for ReflectionTester, which has all these reflection-based annotation methods within it.
NOTE
If you don't want to pick up inherited annotations, you can use get DeclaredAnnotations( ) instead of getAnnotations( ).
package com.oreilly.tiger.ch06; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.AnnotatedElement; import java.lang.annotation.Annotation; public class ReflectionTester { public ReflectionTester( ) { } public void testAnnotationPresent(PrintStream out) throws IOException { Class c = Super.class; boolean inProgress = c.isAnnotationPresent(InProgress.class); if (inProgress) { out.println("Super is In Progress"); } else { out.println("Super is not In Progress"); } } public void testInheritedAnnotation(PrintStream out) throws IOException { Class c = Sub.class; boolean inProgress = c.isAnnotationPresent(InProgress.class); if (inProgress) { out.println("Sub is In Progress"); } else { out.println("Sub is not In Progress"); } } public void testGetAnnotation(PrintStream out) throws IOException, NoSuchMethodException { Class c = AnnotationTester.class; AnnotatedElement element = c.getMethod("calculateInterest", float.class, float.class); GroupTODO groupTodo = element.getAnnotation(GroupTODO.class); String assignedTo = groupTodo.assignedTo( ); out.println("TODO Item on Annotation Tester is assigned to: '" + assignedTo + "'"); } public void printAnnotations(AnnotatedElement e, PrintStream out) throws IOException { out.printf("Printing annotations for '%s'%n%n", e.toString( )); Annotation[] annotations = e.getAnnotations( ); for (Annotation a : annotations) { out.printf(" * Annotation '%s' found%n", a.annotationType( ).getName( )); } } public static void main(String[] args) { try { ReflectionTester tester = new ReflectionTester( ); tester.testAnnotationPresent(System.out); tester.testInheritedAnnotation(System.out); tester.testGetAnnotation(System.out); Class c = AnnotationTester.class; AnnotatedElement element = c.getMethod("calculateInterest", float.class, float.class); tester.printAnnotations(element, System.out); } catch (Exception e) { e.printStackTrace( ); } } }
NOTE
AnnotatedElement is a new interface that the reflection constructs (like Method and Class) implement. It allows access to the new annotation methods used in this code.
The key to much of the code you've just seen is a new interface, java.lang.reflect.AnnotatedElement. In Tiger, the core reflection constructs all implement this interface: Class, Constructor, Field, Method, Package, and AccessibleObject. This allows for the code you've already seen to be introspected for annotationsall these element types provide the following methods as a result of implementing AnnotatedType:
public Annotation getAnnotation(Class annotationType); public Annotation[] getAnnotations( ); public Annotation[] getDeclaredAnnotations( ); public boolean isAnnotationPresent(Class annotationType);
Since any Java program element can be treated as an AnnotatedType, you can always get at an element's annotations using these methods.
NOTE
I've simplified the generics syntax for clarity here. If you're into generics, check out the Javadoc on AnnotatedElement for more details on parameters and return types for these methods.
...annotations that aren't marked as visible at runtime? Recall that you have to explicitly set an annotation's retention to RetentionPolicy.RUNTIME for any of this to work. Even if the annotation is retained at compilation (the default behavior), if the VM doesn't load this retention at class-load time, then reflection can't pick up the annotation. In fact, this is why the Inherited and Documented annotations should always be paired up with the following annotation:
@Retention(RetentionPolicy.RUNTIME)
This ensures that your documentation and/or inheritance is actually readable by code-introspection tools.
|