Getting Started with
the Annotation Processing Tool (apt )
|
What is apt?
The command-line utility apt
, annotation processing
tool, finds and executes annotation processors based on the
annotations present in the set of specified source files being
examined. The annotation processors use a set of reflective APIs
and supporting infrastructure to perform their processing of
program annotations
(JSR 175).
The apt reflective APIs provide a build-time,
source-based, read-only view of program structure. These reflective
APIs are designed to cleanly model the
JavaTM programming language's type system
after the addition of generics (JSR 14). First,
apt
runs annotation processors that can produce new
source code and other files. Next, apt
can cause
compilation of both original and generated source files, thus
easing the development cycle.
Why Should I Use apt?
Many of the intended use cases for annotations involve having
annotations in a base file hold information that is used to
generate new derived files (source files, class files, deployment
descriptors, etc.) that are logically consistent with the base file
and its annotations. In other words, instead of manually
maintaining consistency among the entire set of files, only the
base file would need to be maintained since the derived files are
generated. The apt tool is designed for creating the
derived files.
Compared to using a doclet to generate the derived files based
on annotations, apt
- has a cleaner model of the declarations and current type
structure of programs
- uses a more contemporary API design, such as returning generic
collections instead of arrays and providing visitors to operate on
declarations and types
- supports recursive processing of newly generated files and can
automatically cause compilation of original and generated source
files
While intended for annotation processing, apt can be used
for other reflective programming tasks too.
How to use apt
Overview
First, apt determines what annotations are present on the
source code being operated on. Next, apt looks for
annotation processor factories you've written. The tool asks
the factories what annotations they process. Then apt asks
a factory to provide an annotation processor if the factory
processes an annotation present in source files being operated on.
Next, the annotation processors are run. If the processors have
generated new source files, apt will repeat this process
until no new source files are generated.
Writing an annotation processor relies on four packages:
Each processor implements the AnnotationProcessor
interface in the package com.sun.mirror.apt. This
interface has one method -- process -- used by the
apt tool to invoke the processor. A processor will
"process" one or more annotation types.
A processor instance is returned by its corresponding factory --
an AnnotationProcessorFactory. The apt tool calls
the factory's getProcessorFor method to get hold of the
processor. During this call, the tool provides an
AnnotationProcessorEnvironment. In the environment the
processor will find everything it needs to get started, including
references to the program structure on which it is operating, and
the means to communicate and cooperate with the apt tool
by creating new files and passing on warning and error
messages.
There are two ways a factory can be found; the factory to use
can be specified via the "-factory" command line
option or the factory can be located during the apt
discovery procedure. Using the "-factory"
option is the simplest way to run a single known factory; this
option may also be used when a factory needs more control over how
it is run. To locate the factories on a particular path, the
discovery procedure retrieves from jar files
META-INF/services information in the format described
below.
To create and use an annotation processor using the
"-factory" option:
- Write an AnnotationProcessorFactory that in turn can
create an AnnotationProcessor for the annotation type(s)
of interest.
- Compile the processors and factories using javac with
tools.jar on the classpath; tools.jar contains
the com.sun.mirror.* interfaces.
- Put the compiled class files, or jar files containing the class
files, on the appropriate path when invoking apt
To create and use an annotation processor with the default
discovery procedure use the same first two steps then,
- Create a UTF-8 encoded text file in META-INF/services
named com.sun.mirror.apt.AnnotationProcessorFactory whose
contents are a list of fully qualified names of the concrete
factory classes, one per line. (This is the same format used by
sun.misc.Service.)
- Package the factory, processor, and META-INF/services
information into a jar file
- Place the jar file on the appropriate path when invoking
apt. The appropriate path is discussed in the Discovery section.
A Simple Sample Annotation Processor
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.*;
import com.sun.mirror.util.*;
import java.util.Collection;
import java.util.Set;
import java.util.Arrays;
import static java.util.Collections.*;
import static com.sun.mirror.util.DeclarationVisitors.*;
/*
* This class is used to run an annotation processor that lists class
* names. The functionality of the processor is analogous to the
* ListClass doclet in the Doclet Overview.
*/
public class ListClassApf implements AnnotationProcessorFactory {
// Process any set of annotations
private static final Collection<String> supportedAnnotations
= unmodifiableCollection(Arrays.asList("*"));
// No supported options
private static final Collection<String> supportedOptions = emptySet();
public Collection<String> supportedAnnotationTypes() {
return supportedAnnotations;
}
public Collection<String> supportedOptions() {
return supportedOptions;
}
public AnnotationProcessor getProcessorFor(
Set<AnnotationTypeDeclaration> atds,
AnnotationProcessorEnvironment env) {
return new ListClassAp(env);
}
private static class ListClassAp implements AnnotationProcessor {
private final AnnotationProcessorEnvironment env;
ListClassAp(AnnotationProcessorEnvironment env) {
this.env = env;
}
public void process() {
for (TypeDeclaration typeDecl : env.getSpecifiedTypeDeclarations())
typeDecl.accept(getDeclarationScanner(new ListClassVisitor(),
NO_OP));
}
private static class ListClassVisitor extends SimpleDeclarationVisitor {
public void visitClassDeclaration(ClassDeclaration d) {
System.out.println(d.getQualifiedName());
}
}
}
}
A number of new language and library features are used in the
sample processor. First, static imports are used so that
the simple name of various utility methods can be used; for
example
"unmodifiableCollection"
instead of
"Collections.unmodifiableCollection".
Second, generic collections are used throughout. The
Arrays.asList method is now a var-args method so it can
accept a comma separated list of strings and create a list with the
desired elements. The Collections.emptySet method is a
generic method and can be used to create a type-safe empty set. The
for loop in the process method is an enhanced for
loop that can iterate over collections.
Specifying the Annotations to Process
To tell the tool what annotations it processes, a factory returns a
collection of import-style strings, as shown in the
example. A particular string entry may have one of the following
three forms:
- *
- Process all annotations. This also processes an empty
list of annotations; in other words, a factory that processes
* could be asked to provide a non-trivial processor even
if no annotations are present. This capability allows the
com.sun.mirror APIs to be used to write general source
code processing tools.
- foo.bar.Baz
- Process an annotation whose canonical name is
"foo.bar.Baz".
- foo.bar.*
- Process annotations whose canonical names start with
"foo.bar.".
The apt tool presents the factory with a set of
annotations for the factory to process. Based on the set of
annotations and the annotation processor environment, the factory
returns a single annotation processor. What if the factory wants to
return multiple annotation processors? The factory can use
com.sun.mirror.apt.AnnotationProcessors.getCompositeAnnotationProcessor
to combine and sequence the operation of multiple annotation
processors.
Specifying the Command Line Options to Recognize
The supportedOptions method allows a factory to
communicate to apt which command line options it
recognizes. Command line options starting with
"-A" are reserved for communicating with
annotation processors. For example, if this factory recognizes
options such as -Adebug and -Aloglevel=3, it will
return the strings "-Adebug" and
"-Aloglevel". In the future, apt may
give an indication if -A options are given that no factory
recognizes.
The apt Command Line
In addition to its own options, the apt tool accepts all
of the command-line options accepted by javac. The
javac options are passed to the final javac call,
if any.
The apt specific options are:
- -s dir
- Specify the directory root under which processor-generated
source files will be placed; files are placed in subdirectories
based on package namespace
- -nocompile
- Do not compile source files to class files.
- -print
- Print out textual representation of specified types; perform no
annotation processing or compilation.
- -A[key[=val]]
- Options to pass to annotation processors -- these are not
interpreted by apt directly, but are made available for
use by individual processors
- -factorypath path
- Specify where to find annotation processor factories; if this
option is used, the classpath is not searched for
factories.
- -factory classname
- Name of AnnotationProcessorFactory to use; bypasses
default discovery process
How apt shares some of javac's options:
- -d dir
- Specify where to place processor and javac generated class
files
- -cp path or -classpath
path
- Specify where to find user class files and annotation processor
factories. If -factorypath is given, the classpath is not
searched for factories.
There are a few apt hidden options that may be useful for
debugging:
- -XListAnnotationTypes
- List found annotation types
- -XListDeclarations
- List specified and included declarations
- -XPrintAptRounds
- Print information about initial and recursive apt
rounds
- -XPrintFactoryInfo
- Print information about which annotations a factory is asked to
process
How the apt Tool Operates
After scanning the source files on the command line to determine
what annotations are present, by default the apt tool
looks for annotation processor factories on the appropriate path.
If the -factorypath option is used, that path is the
appropriate path to search for factories; otherwise, the classpath
is the appropriate path. The factories are queried to determine
what annotations they process. If a factory processes one of the
annotations present, that annotation is considered claimed. Once
all annotations are claimed, the tool does not look for additional
factories. After the annotations are all claimed or no more
factories can be found, apt will call the factories'
getProcessorFor methods, passing in the set of annotations
that factory has claimed. Each factory returns a single processor
to perform the appropriate processing for the set of annotations in
question. After all processors are returned, apt calls
each processor in turn. If any processor generated a new source
file, a recursive round of apt will occur. In recursive
apt rounds, discovery calls getProcessorFor on
any factory that provided a processor in a previous round, even if
that factory processes none of the current annotations. This allows
the factory to register a listener
in subsequent apt rounds; though most factories will
simply return AnnotationProcessors.NO_OP in this case.
After a round where no new source files are generated, apt
will invoke javac on the original and generated source
files. If no processors are found or the processors found don't
process the annotations present, calling apt is
essentially equivalent to calling javac directly on the
source files.
If a factory class is used by more than one round of annotation
processing, the factory class is loaded once and the factory's
getProcessorFor method will be called once per round. This
allows a factory to store static state across rounds.
If the -factory option is used, the named factory is
the only one queried.
Rounds of apt Processing
The first round of apt analyzes the input source files,
runs the discovery procedure, and calls the resulting annotation
processors. The second round of apt analyzes the new
source files produced by the first round (if any), runs the
discovery procedure on those new files, and calls the resulting
annotation processors. Likewise, if the second round has produced
new source files, the third round analyzes the new source, runs
discovery, etc. The apt rounds continue until no new
source files are generated. Finally, after the last round, by
default the apt tool will run javac on the
original and generated source files.
Annotation processors or factories can register listeners for the
end of a round using the addListener method in the
environment. The tool calls the registered listeners after all
annotation processors for that round have run to completion. The
listener is passed information about the status of the round, such
as if any new source files were written, if an error was raised,
and if the just completed round was the last round. Listeners can
be used to write out trailing ends of files when all annotation
processing has completed. The same class can implement both the
AnnotationProcessor and RoundCompleteListener
interfaces so the same object can serve in both contexts.
Return Code
If javac is invoked after the last apt round, the
return code of apt will be the return code of
javac compiling those files. If javac is not
invoked, apt will have a 0 exit status if no errors were
reported, either by the tool itself or by processors. Operating on
malformed or incomplete source files in and of itself is not
sufficient to cause apt to have a nonzero exit status.
Declarations and Types
The mirror API represents source code constructs principally
through the Declaration interface and its hierarchy of
subinterfaces, in the package com.sun.mirror.declaration.
A Declaration represents a program element such as a
package, class, or method, and typically corresponds one-to-one
with a particular fragment of source code. Declarations
are the structures that may be annotated.
Types are represented by the TypeMirror interface and
its hierarchy of subinterfaces in the package
com.sun.mirror.type. Types include primitive types, class
and interface types, array types, type variables, and wildcard
types.
The API is careful to distinguish between declarations and
types. This is most significant for generic types, where a single
declaration can define a whole family of types. For example, the
declaration of the class java.util.Set corresponds to
- the parameterized type
java.util.Set<String>
- the parameterized type
java.util.Set<Number>
- the parameterized type java.util.Set<T>
for some type T other than String or
Number
- the raw type java.util.Set.
A declaration has doc comments, a source position, modifiers, and
annotations. A declaration may have different kinds of names
(simple, qualified). More specific declaration subclasses provide
additional information appropriate for that construct. For example,
class declarations provide access to constructors and the
superclass. A declaration for an enum has a method to
provide the enum constants.
TypeMirrors are used to model the return types,
parameter types, etc., in source code. A TypeMirror for a
reference type provides a mapping from a type to corresponding
declaration; for example from the type mirror for
java.util.Set<String> to the declaration for
java.util.Set.
FAQs
- Do you know about the Debian Advanced Packaging
Tool?
Yes.
- How does an annotation processor compare to a
doclet?
The two entities certainly have some similarities; both use
build-time reflective APIs to process source code. However, the
mirror APIs used in annotation processors better model the current
type system of the Java programming language. Also, by default the
annotation processor(s) to run are determined by the annotations
present in the source code rather than only on a mechanism
analogous to the -doclet option. In other words, instead
of running a single fixed doclet, apt dynamically
chooses potentially multiple processors to run.
- How can I process annotations on local variables?
Annotations on local variables are invisible since the declaration
hierarchy does not model program structure inside a method or
constructor.
- Why are there separate factory and processor interfaces in
com.sun.mirror.apt?
It would be possible to combine determining how to process
and controlling when to process; however, we choose to
distinguish and separate the two steps with factory and processor
interfaces.
- Is there an apt ant task
Not at present.
- What is a mirror?
Somewhere you find a reflection. In reflective programming, a
mirror design maintains a consistent separation between the objects
being represented and the objects doing the representing.
- Why were the annotations a factory supports and the options
a factory supports returned by methods instead of being encoded as
annotations on the factory class?
Implementing an interface is the usual mechanism to indicate a
class has a desired capability, such as providing an indication of
what annotations it processes. There is no language mechanism to
require a class be annotated with a particular annotation type.
Therefore, while it would have been technically possible to encode
such information in annotations, doing so would have lacked the
type safety of just having methods in an interface.
- I'm not familiar with visitors, do I have to use
them?
The visitor pattern is one of the standard patterns in the
"Gang of Four" Design Patterns book. The pattern
provides a powerful mechanism to invoke a type-dependent operation
without explicitly testing for the type. However, using visitors is
not mandatory.
See Also
Copyright © 1993, 2010, Oracle and/or its affiliates. All rights reserved. Please send comments using this Feedback page. |
Java Technology |