Summary
While declaring annotations in Java code has been standardized for some time, annotation processing details are still left to each Java implementation. In an interview with Artima, JSR 269, Pluggable Annotation Processing API, spec lead Joe Darcy explains how standardizing annotation processing leads to a more powerful metaprogramming environment.
Advertisement
Arguably the most popular Java metaprogramming feature, annotations have become the preferred means of simplifying API design. Annotation processing often results in code generation, reducing the need to hand-code repetitive, commonly occurring tasks, such as EJB persistence or Web services.
While JSR 175, A Metadata Facility for the Java Programming Language, standardized how annotations are declared in Java code, annotation processing details were hitherto relegated as an implementation detail. More recently, JSR 269, Pluggable Annotation Processing API, now in proposed final draft, aims to standardize annotation processing as well. An implementation of JSR 269 is currently available as part of the JDK 6 snapshot release.
JSR 269 spec lead and Sun engineer Joe Darcy shared with Artima the benefits of standardizing annotation processing in an email interview:
In JDK 5, JSR 175 defined how to declare annotation types as well as a core reflection API for reading annotations at runtime. However, to allow some exploration of the proper build time API, JSR 175 did not standardize [an annotation processing] API in JDK 5. Instead, the non-standard apt tool and its associated API (com.sun.mirror.*) were provided in Sun's JDK for this purpose.
JSR 269 standardizes such a build-time API; a JDK
6 compiler must support JSR 269 annotation processing. Compared to the apt API, the JSR 269 API is more compact and better able to accommodate future language changes; the implementation should be much faster too.
JSR 269 does not change how annotations can be declared; JDK 6 allows custom annotations to be defined in the same way as [in] JDK 5. However, having a standardized annotation processing available in the JDK will support wider use of annotation processing, expanding how annotations can be used. For example, annotation processors that verify properties of a program, such as obeying naming conventions, can be added to the build process. A sample processor that checks naming conventions is in JDK 6 build 101.
Darcy explained that the JSR 269's API works as a framework that, first, finds out what annotation processors to use to process a given annotation and, second, locates and plugs the given annotation processor into the compiler:
The annotation processors act as plug-ins to the compiler, hence "pluggable annotation processing." The annotation processors state which annotations they process, and the invoker of the framework configures which annotations processors may be available. The annotations processors that can get run depends on what annotations are present in the source code, which processors are configured to be available, and what annotations the available processors process.
Darcy explained the difference between build-time and runtime annotation processing. JSR 269 supports build-time processing:
Build-time annotation processing occurs before compilation as part of the build process. For example, a source file under source code control could reference a superclass or subclass that will be generated by annotation processing. The generated and controlled files are then compiled together to produce the final program. Annotation processors can also be used to check for additional consistency constraints outside of the language specification, such as an integer annotation element being in a given range, like 1 through 10.
Runtime annotation processing uses core reflection to read the annotation values—using methods from the java.lang.reflect.AnnotatedElement interface—and take some appropriate action.
Both kinds of annotation processing are examples of meta-programming, that is, programming based on the structure of a program.
Because annotations are inserted into source code, reading annotations must be done with reference to the structure of a program. Part of the JSR 269 API defines modeling elements that correspond to Java programming language constructs. Annotations use those to convey information about program structure to their processors:
JSR 269 has two parts, an API for declaring and interacting with annotation processors—package javax.annotation.processing—and an API for modeling the Java programming language—package javax.lang.model and its subpackages.
The modeling interfaces are used to create objects representing the source code—or class files—an annotation processor is processing. For example, a type declared in a source file Foo.java would be represented by a javax.lang.model.element.TypeElement object. The methods on that object return the type's name, its modifiers, its superclass (if any), the annotations on that type, and so on.
Since this sounds a bit abstract, we asked Darcy to illustrate with a code example how all this works out in practice. This example assumes an annotation called @Bar, and a processor for that annotation, BarProcessor. The example annotates a class with @Bar, and then runs the annotation processor on that class:
First, here is an annotation, @Bar, that is just a marker:
@interface Bar {}
Next, there is a class annotated with @Bar, SourceAnnotatedWithBar.java:
@Bar
public class SourceAnnotatedWithBar{}
Finally, we define a processor for @Bar. In this example, BarProcessor doesn't actually do anything other than print a message. The annotation processor's getSupportedAnnotationTypes() methods tells annotation processing framework what annotation types the processor supports. In the case of BarProcessor, getSupportedAnnotationTypes() returns "Bar." By extending the AbstractProcessor class, this information can itself be encoded in an annotation:
import java.util.Set;
import java.util.EnumSet;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import static javax.lang.model.SourceVersion.*;
import static javax.lang.model.element.Modifier.*;
import static javax.lang.model.element.ElementKind.*;
import static javax.lang.model.type.TypeKind.*;
import static javax.lang.model.util.ElementFilter.*;
import static javax.tools.Diagnostic.Kind.*;
@SupportedAnnotationTypes("Bar")
@SupportedSourceVersion(RELEASE_6)
public class BarProcessor extends AbstractProcessor {
@Override
public boolean process(
Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
processingEnv.getMessager().
printMessage(NOTE,
"Hello world from BarProcessor");
}
return true; // Claim Bar
}
}
Having defined the annotation processor, we can process SourceAnnotatedWithBar. The following command causes javac to run BarProcessor on SourceAnnnotatedWithBar. This example illustrates that the annotation processor here is javac, and the annotation processor is plugged into javac dynamically. After processing the annotation, javac will generate class file(s) for SourceAnnnotatedWithBar.java, just as it does without an annotation processor:
$ javac -processor BarProcessor -processorpath PathWithBarProcessor SourceAnnnotatedWithBar.java
Note: Hello world from BarProcessor
Instead of naming the processor to run explicitly with the -processor option, the processors can also found by the tool using a service-style lookup as well.
In more interesting cases, BarProcessor could generate source code which would then itself undergo annotation processing before being compiled along with the initial inputs.
JSR 269 promises to be a boon to several metaprogramming tools. We recently covered on these pages Annotations for Java Software Defect Detection, a new JSR (305) with the goal to let developers annotate code in order to aid defect-detection tools, such as FindBugs (JSR 305's spec lead is FindBugs project lead Bill Pugh.)
Do you currently use custom annotations in your projects to automatically generate cookie-cutter code? How do custom annotations compare with other metaprogramming techniques in reducing the amount of code you need to write by hand?