Lombok implementation JSR-269

Lombok implementation JSR-269

Preface

Introduction

Lombok is a handy tool, just like Google Guava, and is highly recommended. Every Java engineer should use it. Lombok is a Java™ utility that helps developers eliminate Java verbosity, especially for plain old Java objects (POJOs). It does this through annotations. By implementing Lombok in their development environment, developers can save a lot of time building methods such as hashCode() and equals(), as well as the time it would take to catalog various accessors and mutators.

How is Lombok implemented?
Create a new test class using Lombok's Getter and Setter annotations and compile it through IDEA

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserInfo {
    private String userId;

    private String userName;
}

Open the UserInfo.class file generated after compilation

It is found that get and set methods have been generated, from which we can infer that Lombok enhances the code at compile time. So how is the enhancement at compile time achieved?

Compile phase

The JSR-269 proposal was proposed and passed in JDK6. The proposal passed a set of standard APIs called "plug-in annotation processors", which can process specific annotations in the code in advance during compilation, thereby affecting the working process of the compiler.
For some underlying implementations, it is generally believed that the implementation is implemented using C++ like a virtual machine, which is not particularly friendly to Java programmers. But the Javac compiler is implemented in Java and is easier to use.
The compilation process of Javac is roughly divided into several steps

  1. Preparation: Initializing the pluggable annotation processor
  2. Parsing and filling symbol table process Lexical and grammatical analysis Constructing abstract syntax tree (AST)
  3. Annotation processing of pluggable annotation processors
  4. Analysis and bytecode generation process
  5. After the syntax tree changes, the symbol table will be parsed and filled again. If the syntax tree does not change, the compiler will no longer operate on the source code character stream, but will be based on the abstract syntax tree.

To sum up, if you want to achieve the effect of Lombok, you only need to comply with JSR-269 and operate on AST during compilation. Of course, not only Lombok can be implemented in this way, for example, FindBug, MapStruct, etc. are also implemented in this way.

accomplish

JCTree

JCTree is the base class of AST elements. To achieve the effect, you only need to add a JCTree node.
JCTree is an abstract class that partially implements

From the class name, you can guess what node is used. Here are a few common explanations
JCStatement declares a syntax tree node
JCBlock syntax block
JCReturn: return statement syntax tree node
JCClassDecl: class definition syntax tree node
JCVariableDecl: Field/variable definition syntax tree node
JCMethodDecl: method definition syntax tree node
JCModifiers: Accessing Flag Syntax Tree Nodes
JCExpression: expression syntax tree node, common subclasses are as follows
JCAssign: Assignment statement syntax tree node
JCIdent: Identifier syntax tree node such as this

TreeMaker

Mainly used to generate syntax tree nodes

Code

First, you need to annotate the class to indicate the scope and duration of the action. The two classes correspond to Lombok's Getter and Setter respectively.

@Target({ElementType.TYPE}) //Annotation added to the class @Retention(RetentionPolicy.SOURCE) //Affects the compilation period public @interface Getter {
}
@Target({ElementType.TYPE}) //Annotation added to the class @Retention(RetentionPolicy.SOURCE) //Affects the compilation period public @interface Setter {
}

To create a new abstract annotation processor, you need to inherit AbstractProcessor. Here, the template method mode is used.

public abstract class MyAbstractProcessor extends AbstractProcessor {
    //Syntax tree protected JavacTrees trees;

    //Build syntax tree node protected TreeMaker treeMaker;

    //Create an object of identifier protected Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.trees = 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) {
        //Get the annotated class Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(getAnnotation());
        //Get the syntax tree set.stream().map(element -> trees.getTree(element)).forEach(jcTree -> jcTree.accept(new TreeTranslator() {
            //Get the class definition @Override
            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                //Get all member variables for (JCTree tree: jcClassDecl.defs) {
                    if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
                        JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
                        jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                    }
                }

                jcVariableDeclList.forEach(jcVariableDecl -> {
                    jcClassDecl.defs = jcClassDecl.defs.prepend(makeMethodDecl(jcVariableDecl));
                });
                super.visitClassDef(jcClassDecl);
            }
        }));
        return true;
    }

    /**
     * Creation method * @param jcVariableDecl
     * @return
     */
    public abstract JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl);

    /**
     * What kind of annotation to get * @return
     */
    public abstract Class<? extends Annotation> getAnnotation();
}

Used to process Setter annotation inheritance MyAbstractProcessor

@SupportedAnnotationTypes("com.ingxx.processor.Setter") //The annotation processor acts on which annotation. You can also rewrite getSupportedAnnotationTypes
@SupportedSourceVersion(SourceVersion.RELEASE_8) //Can handle any version and can also override getSupportedSourceVersion
public class SetterProcessor extends MyAbstractProcessor {

    @Override
    public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        //Generate function body this.name = name;
        statements.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()),treeMaker.Ident(jcVariableDecl.getName()))));
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
        //Generate method return treeMaker.MethodDef(
                treeMaker.Modifiers(Flags.PUBLIC), //Access flag getNewMethodName(jcVariableDecl.getName()), //Name treeMaker.TypeIdent(TypeTag.VOID), //Return type List.nil(), //Generic parameter list List.of(getParameters(jcVariableDecl)), //Parameter list List.nil(), //Exception list body, //Method body null //Default method (may be the default in interface)
        );
    }

    @Override
    public Class<? extends Annotation> getAnnotation() {
        return Setter.class;
    }

    private Name getNewMethodName(Name name) {
        String fieldName = name.toString();
        return names.fromString("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length()));
    }

    private JCTree.JCVariableDecl getParameters(JCTree.JCVariableDecl prototypeJCVariable) {
        return treeMaker.VarDef(
                treeMaker.Modifiers(Flags.PARAMETER), //Access flag prototypeJCVariable.name, //Name prototypeJCVariable.vartype, //Type null //Initialization statement);
    }
}

Used to process Getter annotations inherited from MyAbstractProcessor

@SupportedAnnotationTypes("com.ingxx.processor.Getter") //The annotation processor acts on which annotation. You can also rewrite getSupportedAnnotationTypes
@SupportedSourceVersion(SourceVersion.RELEASE_8) //Can handle any version and can also override getSupportedSourceVersion
public class GetterProcessor extends MyAbstractProcessor {

    @Override
    public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        //Generate function body return this.Field name statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
        //Generate method return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
    }

    @Override
    public Class<? extends Annotation> getAnnotation() {
        return Getter.class;
    }

    private Name getNewMethodName(Name name) {
        String fieldName = name.toString();
        return names.fromString("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length()));
    }
}

Compiling an existing class

javac -cp $JAVA_HOME/lib/tools.jar *.java -d .

Create a new test class. In IDEA, an error will be reported because the get set method cannot be found. You can ignore it.

@Getter
@Setter
public class UserInfo {
    private String userId;

    private String userName;

    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId("001");
        userInfo.setUserName("Dewu");
        System.out.println("id = "+userInfo.getUserId()+" name = "+userInfo.getUserName());
    }
}

Then compile

//Multiple processors are separated by commas javac -processor com.ingxx.processor.GetterProcessor,com.ingxx.processor.SetterProcessor UserInfo.java -d .

Check the compiled file and find that get and set methods have been generated

The above is the details from Lombok to JSR-269. For more information about JS reflection mechanism, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • Solve the error problem of IDEA2020.2 plugin lombok (tested and effective)
  • The Library source does not match the bytecode error problem and solution generated by the Lombok plug-in in IDEA (professional test available)
  • After installing the lombok plugin in IDEA and setting Enable Annotation Processing, the compilation still reports an error solution
  • How to install lombok under IDEA and solve the problem of not finding get and set
  • Solve the problem that IDEA2020.1 is incompatible with Lombok
  • Detailed explanation of the solution to the problem of failure to install lombok plugin in Idea 2019.2

<<:  Detailed analysis of MySQL instance crash cases

>>:  How to run commands on a remote Linux system via SSH

Recommend

Detailed steps to configure MySQL remote connection under Alibaba Cloud

Preface As we all know, by default, the MySQL ins...

3 methods to restore table structure from frm file in mysql [recommended]

When mysql is running normally, it is not difficu...

3 ways to correctly modify the maximum number of connections in MySQL

We all know that after the MySQL database is inst...

Detailed usage of React.Children

Table of contents 1. React.Children.map 2. React....

A graphic tutorial on how to install MySQL in Windows

Abstract: This article mainly explains how to ins...

Several ways to pass data from parent components to child components in Vue

I have been studying the source code of Vue recen...

A brief analysis of the difference between FIND_IN_SET() and IN in MySQL

I used the Mysql FIND_IN_SET function in a projec...

Detailed explanation of common methods of JavaScript String

Table of contents 1. charAt grammar parameter ind...

Manually implement the two-way data binding principle of Vue2.0

In one sentence: Data hijacking (Object.definePro...

Perfect Solution for No rc.local File in Linux

Newer Linux distributions no longer have the rc.l...