Note development learning notes

Posted by mjurmann on Sun, 12 Apr 2020 06:03:40 +0200

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

334 original articles published· Praised 373· 1.72 million visitors+
His message board follow

Topics: Java Lombok SpringBoot