Preface
Because of playing the play framework before, I don't know much about springboot;
I have many doubts:
① How can custom annotations work in springboot
② How does the annotation plug-in like Lombok change the generated code
Lombok plug-in
Today, I found an online post called "Lombok", and I understood my doubts
=-=-=-=-=-=-=-=-=-=-=-=-=-=-The following contents are reprinted-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Reference address:
99% of programmers are using Lombok. How simple is the principle? I rolled one too! |Suggested collection
We implement a simple version of Lombok to customize a Getter method. Our implementation steps are:
① A user-defined annotation label interface and a user-defined annotation processor are implemented;
② Using javac api of tools.jar to deal with AST (abstract syntax tree)
③ Compile code using a custom annotation processor.
1. Define custom annotation and annotation processor
First, create a MyGetter.java to customize a comment. The code is as follows:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.SOURCE) // Annotations are reserved only in source code @Target(ElementType.TYPE) // Used to decorate a class public @interface MyGetter { // Define Getter }
Then a custom annotation processor is implemented. The code is as follows:
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.code.Type; 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.TreeTranslator; import com.sun.tools.javac.util.*; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import java.util.Set; @SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes("com.example.lombok.MyGetter") public class MyGetterProcessor extends AbstractProcessor { private Messager messager; // The private JavacTrees javacTrees; // Provides an abstract syntax tree to be processed private TreeMaker treeMaker; // Some methods of creating AST nodes are encapsulated private Names names; // Provides a way to create identifiers @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.messager = processingEnv.getMessager(); this.javacTrees = JavacTrees.instance(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); this.treeMaker = TreeMaker.instance(context); this.names = Names.instance(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MyGetter.class); elementsAnnotatedWith.forEach(e -> { JCTree tree = javacTrees.getTree(e); tree.accept(new TreeTranslator() { @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil(); // Find all variables in the abstract tree for (JCTree jcTree : jcClassDecl.defs) { if (jcTree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); } } // Operation of generating method for variables jcVariableDeclList.forEach(jcVariableDecl -> { messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed"); jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl)); }); super.visitClassDef(jcClassDecl); } }); }); return true; } private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) { ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); // Generate an expression such as this.a = a; JCTree.JCExpressionStatement aThis = makeAssignment(treeMaker.Select(treeMaker.Ident( names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName())); statements.append(aThis); JCTree.JCBlock block = treeMaker.Block(0, statements.toList()); // Generating input JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), jcVariableDecl.getName(), jcVariableDecl.vartype, null); List<JCTree.JCVariableDecl> parameters = List.of(param); // Generate return object JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType()); return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), methodType, List.nil(), parameters, List.nil(), block, null); } private Name getNewMethodName(Name name) { String s = name.toString(); return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length())); } private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) { return treeMaker.Exec( treeMaker.Assign( lhs, rhs ) ); } }
The user-defined annotation processor is the top priority of our simple version of Lombok. We need to inherit the AbstractProcessor class and rewrite its init() and process() methods. In the process() method, we first query all variables and add corresponding methods to variables. We use TreeMaker objects and Names to handle AST, as shown in the code above.
After these codes are written, we can add a Person class to try our customized @ MyGetter function. The codes are as follows:
@MyGetter public class Person { private String name; }
2. Compile code with a custom annotation processor
After all the above processes are executed, we can compile the code to test the effect. First, we go to the root of the code and execute the following three commands.
The root directory entered is as follows:
① Compiling a custom annotator using tools.jar
javac -cp $JAVA_HOME/lib/tools.jar MyGetter* -d .
On the window, I use:
javac -cp "D:\Program Files\Java\jdk1.8.0_111\lib\tools.jar" MyGetter* -d .
Note: a "." at the end of the command indicates the current folder.
② Compile the Person class using the custom annotator
javac -processor com.example.lombok.MyGetterProcessor Person.java
③ View Person source code
javap -p Person.class
I used IDEA to see:
I don't know much about it