Romain Rolland said: there is only one heroism in the world, is to see the truth of life after still love life.

I’m sure most of us are familiar with Lombok, but we don’t know much about its implementation and its drawbacks. This article will take a look at Lombok’s principles, hand you a simple version of Lombok, and give you an understanding of the implementation principles behind this popular technology, as well as its pros and cons.

Introduction to the

Before we get to the principles, let’s review Lombok (older drivers can skip this to the principles section).

Lombok is a very popular open source project (github.com/rzwitserloo…) Using it can effectively solve the tedious and repetitive code in Java projects, such as setters, getters, toString, equals, hashCode, and non-null judgment, which can be effectively solved with Lombok.

use

1. Add the Lombok plug-in

The Lombok plug-in must be installed in the IDE to properly invoke Lombok decorated code. For example, in Idea, add the following steps:

  • Click File > Settings > Plugins to go to the plug-in management page
  • Click Browse repositories…
  • Search Lombok Plugin
  • Click Install Plugin to Install the plug-in
  • Restart the IntelliJ IDEA

The installation is complete, as shown in the figure below:

2. Add Lombok libraries

Next we need to add the latest Lombok libraries to the project. If it is a Maven project, add the following configuration directly to pom.xml:

<dependencies>
  <! -- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.12</version>
		<scope>provided</scope>
	</dependency>
</dependencies>
Copy the code

If JDK 9+ can be added in module mode, the configuration is as follows:

<annotationProcessorPaths>
	<path>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.12</version>
	</path>
</annotationProcessorPaths>
Copy the code

3. Use Lombok

Now to the most important part of the first half of Lombok, let’s look at the code before we use Lombok:

public class Person {
    private Integer id;
    private String name;
    public Integer getId(a) {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName(a) {
        return name;
    }
    public void setName(String name) {
        this.name = name; }}Copy the code

Here’s the code after using Lombok:

@Getter
@Setter
public class Person {
    private Integer id;
    private String name;
}
Copy the code

You can see that after Lombok, all the previous Getter/Setter code was done with a single annotation, making the code instantly much more elegant.

All Lombok notes are as follows:

  • val: is used before a local variable to declare the variable final;
  • @NonNullA NullPointerException (NPE) is thrown if the parameter is null.
  • @CleanupResources are automatically managed, used in front of local variables, and resources are automatically cleaned up before the end of execution in the scope of the current variable. Code such as try-finally is automatically generated to close streams.
  • @Getter/@SetterFor properties, you no longer have to write your own setters and getters, and you can specify access scope.
  • @ToString: You can override toString (exclude= “id”) automatically on a class. For example, @toString (exclude= “id”) excludes the id attribute. Or @toString (callSuper=true, includeFieldNames=true) calls the ToString method of the superclass, containing all the attributes;
  • @EqualsAndHashCode: Used to automatically generate equals and hashCode methods on a class
  • @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructorIf you specify a staticName=”of”, you will also generate a static factory method that returns the class object. This is much more convenient than using a constructor.
  • @DataThe: annotations are the class equivalent of using @ToString, @EqualSandHashCode, @Getter, @setter, and @requiredargsConstrutor, which are useful for POJO classes;
  • @ValueFor classes, this is an immutable form of @data, which is equivalent to adding final declarations to properties, providing only getters, but not setters;
  • @BuilderPerson.builder().name(” XXX “).city(” XXX “).build(); person.builder ().name(” XXX “).build();
  • @SneakyThrowsThrows the checked exception automatically without using the throws statement explicitly on the method.
  • @Synchronized: is used on methods to declare them synchronous and automatically lock them. The lock object is a private propertyA side effect of locking this or an object of its own class is that you cannot prevent uncontrolled code from locking this or an object of its own class, which may result in race conditions or other thread errors.
  • @Getter(lazy=true): can replace the classic Double Check Lock boilerplate code;
  • @Log: Generate different types of log objects based on different annotations, but the instance name is log. There are six optional implementation classes
    • @CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
    • @Log Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName());
    • @Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class);
    • @Log4j2 Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
    • @Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
    • @XSlf4j Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

Their specific use is as follows:

1) use val

val sets = new HashSet<String>();  
/ / equivalent to
final Set<String> sets = new HashSet<>();
Copy the code

(2) use NonNull

public void notNullExample(@NonNull String string) { string.length(); Public void notNullExample(String String) {if(string ! = null) { string.length(); }else {
        throw new NullPointerException("null"); }}Copy the code

(3) use the Cleanup

public static void main(String[] args) { try { @Cleanup InputStream inputStream = new FileInputStream(args[0]); } catch (FileNotFoundException e) { e.printStackTrace(); } InputStream = null; try { inputStream = new FileInputStream(args[0]); } catch (FileNotFoundException e) { e.printStackTrace(); } finally {if(inputStream ! = null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); }}}}Copy the code

(4) use Getter/Setter

@Setter(AccessLevel.PUBLIC)
@Getter(AccessLevel.PROTECTED)
private int id;
private String shap;
Copy the code

5. Use the ToString

@ToString(exclude = "id", callSuper = true, includeFieldNames = true)
public class LombokDemo {
    private int id;
    private String name;
    private int age;
    public static void main(String[] args) {
        // Output LombokDemo(super=LombokDemo@48524010, name=null, age=0)
        System.out.println(newLombokDemo()); }}Copy the code

6 EqualsAndHashCode use

@EqualsAndHashCode(exclude = {"id"."shape"}, callSuper = false)
public class LombokDemo {
    private int id;
    private String shap;
}
Copy the code

⑦ NoArgsConstructor, RequiredArgsConstructor, AllArgsConstructor

@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor
public class LombokDemo {
    @NonNull
    private int id;
    @NonNull
    private String shap;
    private int age;
    public static void main(String[] args) {
        new LombokDemo(1."Java");
        // Use the static factory method
        LombokDemo.of(2."Java");
        // Constructs with no parameters
        new LombokDemo();
        // Include all parameters
        new LombokDemo(1."Java".2); }}Copy the code

Today Builder USES

@Builder
public class BuilderExample {
    private String name;
    private int age;
    @Singular
    private Set<String> occupations;
    public static void main(String[] args) {
        BuilderExample test = BuilderExample.builder().age(11).name("Java").build(); }}Copy the code

Pet-name ruby SneakyThrows use

public class ThrowsTest {
    @SneakyThrows(a)public void read(a) {
        InputStream inputStream = new FileInputStream("");
    }
    @SneakyThrows
    public void write(a) {
        throw new UnsupportedEncodingException();
    }
    / / equivalent to
    public void read(a) throws FileNotFoundException {
        InputStream inputStream = new FileInputStream("");
    }
    public void write(a) throws UnsupportedEncodingException {
        throw newUnsupportedEncodingException(); }}Copy the code

Attending a Synchronized using

public class SynchronizedDemo {
    @Synchronized
    public static void hello() {
        System.out.println("world"); } private static final Object$LOCK = new Object[0];
    public static void hello() {
        synchronized ($LOCK) {
            System.out.println("world"); }}}Copy the code

Tick Getter(lazy = true) use

public class GetterLazyExample {
    @Getter(lazy = true)
    private final double[] cached = expensive();
    private double[] expensive() {
        double[] result = new double[1000000];
        for (int i = 0; i < result.length; i++) {
            result[i] = Math.asin(i);
        }
        returnresult; }} / / equivalent to import the Java. Util. Concurrent. Atomic. AtomicReference; public class GetterLazyExample { private final AtomicReference<java.lang.Object> cached = new AtomicReference<>(); public double[]getCached() {
        java.lang.Object value = this.cached.get();
        if (value == null) {
            synchronized (this.cached) {
                value = this.cached.get();
                if(value == null) { final double[] actualValue = expensive(); value = actualValue == null ? this.cached : actualValue; this.cached.set(value); }}}return (double[]) (value == this.cached ? null : value);
    }
    private double[] expensive() {
        double[] result = new double[1000000];
        for (int i = 0; i < result.length; i++) {
            result[i] = Math.asin(i);
        }
        returnresult; }}Copy the code

The principle of analysis

We know that the Java compilation process can be roughly divided into three phases:

  1. Parse and populate the symbol table
  2. Annotation processing
  3. Analysis and bytecode generation

The compilation process is shown in the figure below:

This can be verified in a program, such as the code that this article started with @data:

After we compiled, we looked at the compiled source code for the Person class and it looked like this:

The execution process of Lombok is as follows:

A Lombok

We implement a simple version of Lombok to customize a Getter method. Our implementation steps are:

  1. Customize an annotation label interface, and implement a custom annotation handler;
  2. Using tools.jar’s Javac API to process the AST (abstract syntax tree)
  3. Compile the code using a custom annotation handler.

This will allow you to implement a simple version of Lombok.

1. Define custom annotations and annotation handlers

First create a mygetter. Java custom annotation 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 the source code
@Target(ElementType.TYPE) // Used to decorate classes
public @interface MyGetter { / / define Getter

}
Copy the code

Implement a custom annotation handler 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; // Log input at compile time
    private JavacTrees javacTrees; // Provides an abstract syntax tree to work with
    private TreeMaker treeMaker; // Encapsulates some methods for creating AST nodes
    private Names names; // Provides a method for creating 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); }}// Generate method operations on 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());

        // Generate the input parameters
        JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),
                jcVariableDecl.getName(), jcVariableDecl.vartype, null);
        List<JCTree.JCVariableDecl> parameters = List.of(param);

        // Generate the 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) {
        returntreeMaker.Exec( treeMaker.Assign( lhs, rhs ) ); }}Copy the code

We need to inherit from AbstractProcessor and override its init() and process() methods. In the process() method, we first query all variables. Add methods to variables. We use TreeMaker objects and Names to process the AST, as shown in the code above.

With this code in place, we can add a new Person class to try our own @myGetter function. The code looks like this:

@MyGetter
public class Person {
    private String name;
}
Copy the code

2. Compile the code with a custom annotation handler

After all of the above processes are executed, we can compile the code to test the effect. First, go to the root directory of your code and execute the following three commands.

The root directory is as follows:

Use tools.jar to compile a custom annotator

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

Note: the command has a ‘. ‘at the end. Represents the current folder.

Compile the Person class using a custom annotator

javac -processor com.example.lombok.MyGetterProcessor Person.java

③ View the Person source code

javap -p Person.class

The source file is as follows:

You can see that our custom getName() method has been successfully generated, and at this point the simple version of Lombok is complete.

Lombok pros and cons

The advantages of Lombok are obvious: it allows us to write less code, saves development time, and makes code look more elegant. The disadvantages are as follows.

Disadvantage 1: Reduced debugging

Lombok automatically generates a lot of code, but this code is generated at compile time, so it can be “lost” during development and debugging, which makes debugging code a major inconvenience.

Disadvantage 2: Compatibility issues may occur

Lombok is very invasive to code, and the JDK version is released every six months, and Lombok is a third-party project and maintained by an open source team, so there is no way to ensure version compatibility and iteration speed, which may lead to version incompatibilities.

Weakness 3: It may pit teammates

This is especially true for new members of the group. If they have not used Lombok before, when they pull down the code, they will be prompted with error messages such as “no method found” when compiling the project because no Lombok plug-in has been installed. As a result, the project will fail to compile and the collaboration between the unity members will be affected.

Disadvantage 4: Broken encapsulation

The definition of object-oriented encapsulation is that the internal data is hidden through access control, and the external can only access and modify the internal data through the limited interface provided by the class.

In other words, we should not mindfully use Lombok to expose Getter/Setter methods for all fields, because some fields, such as the number of items in the shopping cart, are not allowed to be modified directly under certain circumstances, which directly affects the details of the purchase and the total price, so we should provide a uniform method for modifying them. Make associated changes, rather than adding methods to access and modify each field.

summary

In this article we describe the use and implementation of Lombok, which is JSR 269 implemented via JDK 6: The Pluggable Annotation Processing API converts Lombok’s annotations to Java’s regular methods at compile time by inheriting AbstractProcessor class, Override its init() and process() methods to implement a simple version of Lombok. However, Lombok also has some disadvantages, such as reduced debuggability and possible compatibility issues, so we have to choose whether to use Lombok and how to use Lombok according to our own business scenarios and actual conditions.

Finally, a word of caution, no matter how good the technology is not the panacea, as if no matter how good the shoes also have to fit their feet!

Thanks for reading, and I hope you were inspired by this article. If you think it’s good, share it with friends in need. Thank you.

Reference & thanks

Juejin. Im/post / 684490…

www.tuicool.com/articles/y6…

For more exciting content, please follow the wechat official account “Java Chinese Community”