6.11 Reflecting on Annotations
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.
6.11.1 How do I do that?
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 {NOTEThis code is in the
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");
}
}
ReflectionTester
class.Running this code gives you the following output:
run-ch06:Additionally, this approach lets you take advantage of the Inherited
[echo] Running Chapter 6 examples from Java Tiger: A Developer's
Notebook
[echo] Running ReflectionTester...
[java] Super is In Progress
annotation, described in Setting Up Inheritance in Annotations:
public void testInheritedAnnotation(PrintStream out) throws IOException {NOTEThis assumes you
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");
}
}
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:Although this is not picked up in the Javadoc, it certainly appears in your
[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
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:NOTEReflection on
annotations only
works for annotation
types that
have Runtime
retention.
public void testGetAnnotation(PrintStream out)Once the method in question is located (in this case,
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 + "'");
}
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:To use this code, you obviously have to know exactly what you're looking
[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'
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:NOTEThe 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)If you supplied this method the calculateInterest( ) method from
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( ));
}
}
AnnotationTester, you'd get the following output:
run-ch06:This code is really pretty straightforward, so I'll leave it to you to work
[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
through the details. Example 6-15 is the complete code listing for
ReflectionTester, which has all these reflection-based annotation
methods within it.NOTEIf you don't want
to pick up
inherited
annotations, you
can use get
DeclaredAnnotations(
)
instead of
getAnnotations( ).
Example 6-15. Testing reflection-based annotation methods
package com.oreilly.tiger.ch06;NOTEAnnotatedElement
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( );
}
}
}
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.
6.11.2 What just happened?
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);Since any Java program element can be treated as an AnnotatedType,
public Annotation[] getAnnotations( );
public Annotation[] getDeclaredAnnotations( );
public boolean isAnnotationPresent(Class annotationType);
you can always get at an element's annotations using these methods.NOTEI'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.
6.11.3 What about...
...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.
|