Pluggable annotation processing API

   after JDK 1.5, the Java language provides support for annotations, which, like ordinary java code, play a role during runtime. JSR-269: Pluggable Annotations Processing API of JSR-269 specification is implemented in JDK 1.6. Provides a set of standard APIs for plug-in Annotation processors that process annotations during compilation. The Annotation Processor processes annotations during compilation rather than during runtime. The Annotation Processor is equivalent to a plug-in of the compiler, so it is called plug-in Annotation processing In these plug-ins, you can read, modify and add any element in the abstract syntax tree. If these plug-ins modify the syntax tree during Annotation processing, the compiler will return to the process of parsing and filling the symbol table for reprocessing until all plug-in Annotation processors have not modified the syntax tree. Each cycle is called a Round, that is, the loopback process in the first figure. With the standard API for compiler Annotation processing, our code may interfere with the behavior of the compiler. Since any element in the syntax tree, even code annotations, can be accessed in the plug-in, the plug-in implemented by the plug-in Annotation Processor has a lot of room for function. With enough creativity, programmers can use plug-in Annotation processors to achieve many things that can only be done in coding.

From the code of Sun Javac, the compilation process can be roughly divided into three processes:

  • Parsing and filling symbol table process
  • Annotation processing process of plug-in annotation processor
  • Analysis and bytecode generation process

The entry of Javac compilation action is com sun. tools. Javac. main. Java compiler class. The code logic of the above three processes is concentrated in the compile() and compile2() methods of this class. In the JavaCompiler source code, the initialization process of the plug-in annotation processor is completed in the initPorcessAnnotations() method, and its execution process is completed in the processAnnotations() method. This method determines whether there are new annotation processors to execute. If so, use COM. Com sun. tools. Javac. processing. The doProcessing() method of the javacprocessingenvironment class generates a new JavaCompiler object to process the subsequent steps of compilation.

When the compiler compiles with the default compilation parameters, it performs the following steps:

1.1. Parse: read in a pile of * java source code, and map the read Token to the AST node.
1.2. Enter: put the class definition in the Symbol Table.
2. Process annotations: optional. Process the annotations found in the compilation units.
3.1. Attribute: add attribute for AST. This step includes name resolution, type checking, and constant fold.
3.2. Flow: perform Flow analysis for the AST obtained earlier. This step includes the check of assignment and the check of enforceability.
3.3. Desugar: Rewrite AST and convert some complex syntax into general syntax.
3.4. Generate: generate source files or class files.

Annotation processors have three main uses.

  • First, define Compilation Rules and check the compiled source files.
  • Second, modify the existing source code.
  • Third, generate new source code.

The second involves the internal API of the Java compiler, so it is not recommended. The third is the OpenJDK tool jcstress and the way JMH generates test code.

Write plug-in processor

Write a plug-in annotation processor. The key points are as follows:

  • Override the init method to get some necessary build objects
  • Rewrite the process method to realize the Builder logic
  • Use the @ SupportedAnnotationTypes annotation to indicate the annotation type of interest
  • Use the @ SupportedSourceVersion annotation to indicate the source code version
package foo;

import java.lang.annotation.*;

@Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.SOURCE)
public @interface CheckGetter {
}
package bar;

import java.util.Set;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;

import foo.CheckGetter;

@SupportedAnnotationTypes("foo.CheckGetter")
@SupportedSourceVersion(SourceVersion.RELEASE_10)
public class CheckGetterProcessor extends AbstractProcessor {

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    // TODO: annotated ElementKind.FIELD
    for (TypeElement annotatedClass : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(CheckGetter.class))) {
      for (VariableElement field : ElementFilter.fieldsIn(annotatedClass.getEnclosedElements())) {
        if (!containsGetter(annotatedClass, field.getSimpleName().toString())) {
          processingEnv.getMessager().printMessage(Kind.ERROR,
              String.format("getter not found for '%s.%s'.", annotatedClass.getSimpleName(), field.getSimpleName()));
        }
      }
    }
    return true;
  }

  private static boolean containsGetter(TypeElement typeElement, String name) {
    String getter = "get" + name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
    for (ExecutableElement executableElement : ElementFilter.methodsIn(typeElement.getEnclosedElements())) {
      if (!executableElement.getModifiers().contains(Modifier.STATIC)
          && executableElement.getSimpleName().toString().equals(getter)
          && executableElement.getParameters().isEmpty()) {
        return true;
      }
    }
    return false;
  }
}
package foo;     // PackageElement

class Foo {      // TypeElement
  int a;           // VariableElement
  static int b;    // VariableElement
  Foo () {}        // ExecutableElement
  void setA (      // ExecutableElement
    int newA         // VariableElement
  ) {}
}

To Debug the plug-in annotation processor, the most convenient way is to perform compilation in java code.

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int compilationResult = compiler.run(null, null, null, '/path/to/Foo.java');
// javac -cp /CLASSPATH/TO/CheckGetterProcessor -processor bar.CheckGetterProcessor Foo.java

reference

  1. Java-JSR-269-plug in annotation processor
  2. Lombok principle analysis and function realization
  3. Deep disassembly of Java virtual machine - annotation processor

Keywords: Java Back-end

Added by hwttdz on Sat, 08 Jan 2022 16:55:32 +0200