Working with Annotation Processing
1. Understanding Annotation Processor Lifecycle
javac → discover processors (META-INF/services) → init()
→ for each round: process(annotations, RoundEnv)
→ generate sources → re-run with new sources → ...
→ final round (processingOver()==true)
| Phase | Hook |
|---|---|
| Init | init(ProcessingEnvironment) |
| Per round | process() |
| Supported | getSupportedAnnotationTypes() |
| Source level | getSupportedSourceVersion() |
2. Creating AbstractProcessor Subclass
| Annotation | Effect |
|---|---|
@SupportedAnnotationTypes | Declarative supported types |
@SupportedSourceVersion | Java version |
@SupportedOptions | Compiler -A options |
Example: Skeleton
@SupportedAnnotationTypes("com.acme.Audit")
@SupportedSourceVersion(SourceVersion.RELEASE_21)
public class AuditProcessor extends AbstractProcessor {
@Override public boolean process(Set<? extends TypeElement> ann, RoundEnvironment env) {
for (Element e : env.getElementsAnnotatedWith(Audit.class)) { /* ... */ }
return true;
}
}
3. Using ProcessingEnvironment
| Service | Use |
|---|---|
getElementUtils() | Element queries |
getTypeUtils() | Type relations (subtype, erasure) |
getFiler() | Generate files |
getMessager() | Report errors/warnings |
getOptions() | -A key=value |
4. Accessing RoundEnvironment
| Method | Returns |
|---|---|
getElementsAnnotatedWith(A) | Set<Element> |
getRootElements() | Top-level types this round |
processingOver() | Last round? |
errorRaised() | Compilation has error |
5. Generating Source Files (Filer)
| Method | Output |
|---|---|
createSourceFile(name) | .java file |
createClassFile(name) | .class file |
createResource(loc, pkg, file) | Arbitrary resource |
6. Using JavaPoet for Code Generation
| Builder | Use |
|---|---|
TypeSpec.classBuilder("X") | Class |
MethodSpec.methodBuilder("m") | Method |
FieldSpec.builder(T, "f") | Field |
JavaFile.builder(pkg, type).build() | Compilation unit |
$T, $L, $S, $N | Type, literal, string, name placeholders |
Example: Generate hello class
TypeSpec hello = TypeSpec.classBuilder("Hello")
.addModifiers(Modifier.PUBLIC)
.addMethod(MethodSpec.methodBuilder("greet")
.returns(String.class)
.addStatement("return $S", "hi")
.build())
.build();
JavaFile.builder("gen", hello).build().writeTo(filer);
7. Handling Compilation Errors (Messager)
| Kind | Effect |
|---|---|
ERROR | Fails compilation |
WARNING / MANDATORY_WARNING | Visible warning |
NOTE | Info only |
| Element-anchored | Pass Element for IDE jump |
8. Configuring Annotation Processors
| Build Tool | Mechanism |
|---|---|
| Maven | maven-compiler-plugin <annotationProcessorPaths> |
| Gradle | annotationProcessor "group:artifact" |
| javac | -processor com.x.P -Akey=val |
9. Using Service Provider Interface
| Mechanism | Detail |
|---|---|
| File | META-INF/services/javax.annotation.processing.Processor |
| Content | FQN of processor (one per line) |
| Auto | Use Google AutoService @AutoService |
10. Debugging Annotation Processors
| Approach | Setup |
|---|---|
| Remote debug | -J-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 |
| IDE | Run javac as test in IDE; attach debugger |
| Log via Messager | NOTE level prints to compile output |
11. Understanding Incremental Processing
| Tool | Mode |
|---|---|
| Gradle | isolating (one input → outputs) or aggregating |
| Declaration | Resource META-INF/gradle/incremental.annotation.processors |
| Benefit | Skip processor for unchanged inputs |
| Pitfall | Aggregating processors invalidate widely |