1. Introduction of APT

1.1 What is APT?

APT(Annotation Processing Tool) Annotation – based Processing Tool. Using this technique, you can do things like code staking and code generation during Java code compilation. The most common use in practice is AbstractProcessor

1.2 Compilation Process

When the JVM executes code, it executes.class bytecode files. So you need to use a compiler to compile.java files into binary bytecode files. The official compiler is the javac command.

When you execute the javac command

  • The first step:javacThe command initiates a full JVM to perform compilation. (Yes, you read that right, javac uses the JVM for compilation)
  • Step 2: Load yours.javaFile, and interpret it as an AST(Abstract Syntax Tree)To understand), all syntax errors are at this stage.
  • Step 3: The JVM then iterates through the AST using visitor pattern to add code meaning to the syntax tree,ProcessorThe annotation handler is executed at this stage. Eventually the AST is compiled and saved as.classBinary bytecode files.

Processor:

Comments are processed in rounds order. In each round, the processor can be asked for a subset of the annotations that Process found on the source and class files produced in the previous round. The input processed in the first round is the initial input for the tool to run; These initial inputs can be treated as outputs of virtual round 0 processing. If the processor is asked to process on a given round, it is asked to process subsequent rounds, including the last round, even if there are no comments to process. The tool infrastructure can also require the processor to process files implicitly generated by the tool’s operations.

Annotation processing happens in a sequence of rounds. On each round, a processor may be asked to process a subset of the annotations found on the source and class files produced by a prior round. The inputs to the first round of processing are the initial inputs to a run of the tool; these initial inputs can be regarded as the output of a virtual zeroth round of processing. If a processor was asked to process on a given round, it will be asked to process on subsequent rounds, including the last round, even if there are no annotations for it to process. The tool infrastructure may also ask a processor to process files generated implicitly by the tool’s operation.

1.3 Processor

The class in the javax.mail. The annotation. Processing package, it is designed to compile time for some annotations for processing, such as generating new class or modify an existing class. For example, Lombok’s Getter and Setter method generation is based on this technique.

Method description:

The method name describe
getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) Returns an empty iteration completion.
getSupportedAnnotationTypes() If the processor class annotates with SupportedAnnotationTypes, an immutable set with the same set of strings as the annotation is returned.
getSupportedOptions() If the processor class annotates with SupportedOptions, an unmodifiable set with the same set of strings as the annotation is returned
getSupportedSourceVersion() If the processor class is annotated with SupportedSourceVersion, return the source version in the annotation.
init(ProcessingEnvironment processingEnv) Initialize the processor using the processing environment by setting the processingEnv field to the value of the processingEnv parameter.
process(Set annotations, RoundEnvironment roundEnv) Processes a set of annotation types from the type elements of the previous round and returns whether these annotation types were declared by this handler. If true is returned, annotation types are declared and subsequent handlers are not required to process them; If false is returned, annotation types are unclaimed and subsequent processors may be required to process them. The processor can always return the same Boolean value, or it can change the result according to its own criteria of choice.

ProcessingEnviroment

This class contains all the utility classes used during compilation.

Utility class The method name function
Filer getFiler File stream output path. When we generate a Java class using AbstractProcess, we need to save it in the directory specified by Filer.
Messager getMessager Output log tool, we need to output some log related to use this.
Elements getElementUtils Tools to obtain element information, such as some class information inheritance relationships, etc.
Types getTypeUtils Type-specific utility classes

1.3 AST(Abstract Syntax Tree) Abstract Syntax Tree

Each. Java file is parsed into a JCCompilationUnit object, and all information about the class is parsed into tree nodes.The meaning of each node is as follows:AST parse

1.4 Generate getters in the current class file by modifying the AST tree (instead of generating a new class file)

1.4.1 Creating a Multi-module project

Apt-core module: the code in this module is the annotation processing class apt-Demo module: the code in this module is the usage class

Why use two modules?

The JDK uses the javac command to compile. The -cp option of this command can introduce additional. Jar files. Therefore, you need to type the annotation handler’s class into a.jar package, so use multiple modules.

1.4.2 Configuring POM.xml to reference private apis

After jdk1.9 oracle removed the tools.jar package, which contains a number of proprietary apis, including JCTree handling, that must be referenced to modify the AST tree. Some students found that the introduction of tools.jar still didn’t work with JCTree, etc. That’s because after java9, the concept of modularity was introduced in the JDK, and the JDK itself was modularized. Java.com Pile is listed as a private API in Java9 and is not exposed. So you can add build parameters to expose these packages as follows

<build> <plugins> <plugin> <artifactId> Maven-compiler-plugin </artifactId> <version>3.8.1</version> <configuration> <compilerArgs> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg> </compilerArgs> <! <target>11</target> <source>11</source> <! <annotationProcessorPaths> <path> <groupId> com.google.auto-service </groupId> <artifactId>auto-service</artifactId> <version>${auto-service.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>Copy the code

1.4.3 Creating an Annotation Class

package com.czl.apt; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * indicates that the Getter annotation can only be annotated on the class */ @target (value = elementType.type) /** * indicates that the Getter annotation will be compiled by the compiler but does not exist at JVM runtime */ @Retention(RetentionPolicy.CLASS) public @interface Getter { }Copy the code

1.4.4 Creating annotation Handling classes

package com.czl.apt; import com.google.auto.service.AutoService; import com.sun.source.tree.Tree; import com.sun.tools.javac.api.JavacTrees; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeScanner; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Names; import javax.annotation.processing.*; import javax.lang.model.element.*; import javax.lang.model.util.Elements; import java.util.*; /** * This annotation indicates that the current annotation processor can only handle * com.ccl.apt. Getter annotations */ @supportedanNotationTypes (" com.ccl.apt. Getter") /** * Javac calls the annotation Processor using the SPI mechanism * so you need to create an SPI file in meta-INF * Using the @autoService annotation you can automatically create * Google tools */ @autoService (processor.class) public class GetterProcessor extends AbstractProcessor { private Elements elementUtils; private JavacTrees javacTrees; private TreeMaker treeMaker; private Names names; /** * initialize, @param processingEnv */ @override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.elementUtils = processingEnv.getElementUtils(); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); //JCTree utility class this.javacTrees = javacTrees. Instance (processingEnv); //JCTree utility class this.treemaker = treeMaker. Instance (context); // Name the utility class this.names = names.instance (context); } /** * key: The Jvm calls this method to perform annotation processing * * @param Annotations annotations supported by the processor * @param roundEnv environment * @return */ @override public Boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //1. Get the TypeElement of the annotation to be processed (iterating through the Annotations input parameter) using the fully qualified class name here to get the TypeElement anno = elementUtils.getTypeElement("com.czl.apt.Getter"); //2. Get annotated elements (these may be methods, interfaces, etc.) Set<? extends Element> types = roundEnv.getElementsAnnotatedWith(anno); ForEach (t -> {//3. If (t.getkind ().isclass ()) {//4. Processing currentClassProcess (TypeElement) (t); }}); /** * Important: If this method returns True, subsequent annotation handlers will not process the annotations * if this method returns False, subsequent annotation handlers will process the annotations * in SPI file order */ return False; } /** * @param typeElement */ private void currentClassProcess(typeElement typeElement) {try { Jctree.jcclassdecl classDecl = javacTrees. GetTree (typeElement); // Access the tree because the tree structure needs to be modified, TreeTranslator classDecl. Accept (new TreeScanner() {@override public void visitDef (jctree.jcclassdecl tree) { List< jctree.jcmethodDecl > methods = new ArrayList<>(); //tree.defs defines traversal for (JCTree varTree: Tree.defs) {// Since only attribute methods are generated this time, So focus only on the property if (vartree.getkind ().equals(tree.kind.variable)) {jcTree.jcVariableDecl JCVariableDecl = (JCTree.JCVariableDecl) varTree; methods.add(generateGetMethods(jcVariableDecl)); }} /** * The prependList method adds parameters to the head of the list and returns a new list * here adds the generated method to the method definition */ tree.defs = tree.defs.prependList(com.sun.tools.javac.util.List.from(methods)); super.visitClassDef(tree); }}); } catch (Exception e) { System.out.println(1); Private jctree.jcmethodDecl generateGetMethods(jctree.jcvariableDecl variableDecl) { //treeMaker.MethodDef creates method jcTree.jcmethodDecl methodDecl = treeMaker.MethodDef(// creates method accessor public TreeMaker.Modifiers(flags.public), // names.fromString("get" + variabledecl.getName ().tostring ()), / / the return value treeMaker. Ident (variableDecl. Vartype. The tsym), / / generic parameter list Here to write without com. Sun. View javac. Util. List. Nil (), / / into the parameter list Here to write without com. Sun. View javac. Util. List. Nil (), / / exception Here to write without com. Sun. View javac. Util. List. Nil (), // Method body treeMaker.Block(// I don't know what this is 0, // statement list to build return this.id; New ListBuffer< jctree.jcStatement >().append(// return treeMaker.Return(// here is to build this.id; Select(treemaker.ident (names.fromString("this")), variableDecl.name))).tolist ()), // default null is not required); return methodDecl; }}Copy the code

1.4.4 results

GitHub project address