Method decorators

Combine before/around/after slots in a single processor to stack several method-level annotations.

BasicStaticProxyAnnotationProcessor provides three composition slots for method-level annotations: beforeDelegation, aroundDelegation and afterDelegation. They let one processor handle several annotations on the same method without forking each combination by hand.

The three slots

@Override
protected List<CodeBlock> beforeDelegation(ExecutableElement methodElement,
                                           String methodName2Delegate,
                                           TypeElement targetType) {
  if (hasAnnotation(methodElement, RequiresRole.class.getCanonicalName())) {
    return List.of(CodeBlock.of("securityEnforcer.requireRole();\n"));
  }
  return List.of();
}

@Override
protected Optional<CodeBlock> aroundDelegation(ExecutableElement methodElement,
                                               String methodName2Delegate,
                                               TypeElement targetType) {
  return Optional.of(
      CodeBlock.of("$L;\n",
          delegatorStatementWithReturn(methodElement, methodName2Delegate)));
}

@Override
protected List<CodeBlock> afterDelegation(ExecutableElement methodElement,
                                          String methodName2Delegate,
                                          TypeElement targetType) {
  return List.of(CodeBlock.of("securityAudit.recordSuccess();\n"));
}

beforeDelegation returns blocks to emit before the delegate call. aroundDelegation returns the delegate call itself (with the return value if any). afterDelegation returns blocks to emit after the delegate call.

The generated method body is the concatenation of all three. Each slot is ordered, so you can return multiple blocks per slot to stack contributions from different annotations on the same method.

Composing several annotations

A typical security-oriented target stacks multiple method-level annotations:

public interface AccountService {

  @RequiresPolicy("account")
  @RequiresPermission("account:read")
  @RequiresRole("admin")
  Account loadAccount(String accountId);
}

Inside beforeDelegation you check for each annotation in turn and append its enforcement block. The generated method ends up with:

@Override
public Account loadAccount(String accountId) {
  securityEnforcer.requirePolicy("account");
  securityEnforcer.requirePermission("account:read");
  securityEnforcer.requireRole("admin");
  Account result = delegator.loadAccount(accountId);
  securityAudit.recordSuccess();
  return result;
}

Annotation lookup helpers

The base class provides:

  • annotationsOn(ExecutableElement) — all annotations on the method.
  • annotationsOn(ExecutableElement, Set<String>) — only those whose fully qualified names are in the set.
  • hasAnnotation(ExecutableElement, String) — boolean check by FQCN.

Use these inside the three decorator slots to gate which blocks you emit.

When to use aroundDelegation

The default behaviour is “call the delegate, return its result.” If you only want before/after, leave aroundDelegation at the default (do not override it). Override it when you need to wrap the call in something — a try/finally, a Context.run(() -> ...), a fallback on exception.

Edit this page on GitHub ↗