Basics

The minimum viable annotation processor on top of BasicStaticProxyAnnotationProcessor.

A custom processor extends BasicStaticProxyAnnotationProcessor<T> where T is the annotation you handle. You implement three things: which annotation you are responsible for, what extra class-level bits you want, and how each method body looks.

The smallest useful processor

public final class SecuredAnnotationProcessor
    extends BasicStaticProxyAnnotationProcessor<Secured> {

  @Override
  public Class<Secured> responsibleFor() {
    return Secured.class;
  }

  @Override
  protected void addClassLevelSpecs(TypeElement typeElement, RoundEnvironment roundEnv) {
    // Add fields, marker annotations, helper methods, or constructor details.
  }

  @Override
  protected CodeBlock defineMethodImplementation(ExecutableElement methodElement,
                                                 String methodName2Delegate,
                                                 TypeElement targetType) {
    return CodeBlock.builder()
        .addStatement(delegatorStatementWithReturn(methodElement, methodName2Delegate))
        .build();
  }
}

responsibleFor() tells the base class which annotation to discover. addClassLevelSpecs is your hook for adding state at class level — a field, a marker annotation, a helper. defineMethodImplementation runs once per method on the target interface and returns the generated body.

Choosing the generated class suffix

By default the generated class name is the target name plus a suffix derived from the annotation type. Override it programmatically:

@Override
protected String generatedClassSuffix(TypeElement typeElement) {
  return "Secured";
}

Or use the processor option globally:

-Aproxybuilder.suffix=Secured

Constructor modifiers

If you do not want a public constructor on the generated class:

@Override
protected Set<Modifier> filterConstructorModifiers(Set<Modifier> modifiers) {
  modifiers.remove(Modifier.PUBLIC);
  modifiers.add(Modifier.PROTECTED);
  return modifiers;
}

Static imports

addStaticImports(JavaFile.Builder) is a no-op by default. Override it only when the generated source needs static imports:

@Override
protected void addStaticImports(JavaFile.Builder builder) {
  builder.addStaticImport(MySecurityApi.class, "requireRole");
}

Useful helpers on the base

BasicAnnotationProcessor (the parent of the static-proxy base) exposes:

  • pkgName(TypeElement) — package name of the target type.
  • className(Element) — simple class name.
  • targetClassNameSimpleForGeneratedClass(TypeElement) — name of the generated class.
  • targetClassNameSimpleForSourceClass(TypeElement) — name of the source class.
  • defineDelegatorField(TypeElement) — the standard delegator field declaration.
  • defineSimpleClassNameField(TypeElement) — a static class-name field for logging.
  • defineParamsForMethod(ExecutableElement) — JavaPoet ParameterSpecs for a method.
  • extractArgumentTypeListFromMethod(ExecutableElement) — raw parameter type mirrors.
  • delegatorMethodCall(ExecutableElement, String) — text of the delegator call (no statement terminator).
  • delegatorStatementWithReturn(ExecutableElement, String)return delegator.method(...);
  • delegatorStatementWithOutReturn(ExecutableElement, String)delegator.method(...);
  • delegatorStatementWithLocalVariableResult(ExecutableElement, String)Type result = delegator.method(...);
  • error(Element, String), warning(...), note(...) — compiler diagnostics through Messager.

Discoverability

Annotation processors are discovered through META-INF/services/javax.annotation.processing.Processor. Add your processor’s fully qualified name to that file.

Next

Edit this page on GitHub ↗