Introduction to Lombok, usage, how it works, pros and cons

The Lombok project is a Java library that is automatically inserted into editors and build tools, and Lombok provides a useful set of annotations to remove a lot of boilerplate code from Java classes.


@[TOC]


Introduction to the

The official introduction

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, automate your logging variables, and much more.

After translation:

The Lombok project is a Java library that automatically plugs into your editor and build tools to simplify your Java. Instead of writing another getter, setter, toString, or equals method, your class with an annotation has a full-fledged generator that automates your logging variables, and much more

The website links

use

Adding Maven dependencies

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.1816.</version>
  <scope>provided</scope>
</dependency>
Copy the code

Note: Here scope is set to Provided to prevent dependencies from passing to other projects

Installing plug-ins (optional)

During development, Lombok plug-ins need to be installed in IDEA

Why install plug-ins?

First, the code compiles and runs normally without installing the plug-in. If you do not install the plug-in, IDEA will not automatically indicate boilerplate methods that Lombok generates at compile time, and IDEA will also indicate problems when checking for syntax correctness, with large areas of red code

The sample

Here are two examples of the differences between using Lombok and not using it

Create a user class

Do not use Lombok

public class User {
  private Integer id;
  private Integer age;
  private String realName;

  public User(a) {}@Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null|| getClass() ! = o.getClass()) {return false;
    }

    User user = (User) o;

    if(! Objects.equals(id, user.id)) {return false;
    }
    if(! Objects.equals(age, user.age)) {return false;
    }
    return Objects.equals(realName, user.realName);
  }

  @Override
  public int hashCode(a) {
    intresult = id ! =null ? id.hashCode() : 0;
    result = 31* result + (age ! =null ? age.hashCode() : 0);
    result = 31* result + (realName ! =null ? realName.hashCode() : 0);
    return result;
  }

  @Override
  public String toString(a) {
    return "User{" +
        "id=" + id +
        ", age=" + age +
        ", realName='" + realName + '\' ' +
        '} ';
  }

  public Integer getId(a) {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public Integer getAge(a) {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public String getRealName(a) {
    return realName;
  }

  public void setRealName(String realName) {
    this.realName = realName; }}Copy the code

Use Lombok

@Data
public class User {
  private Integer id;
  private String username;
  private Integer age;
}
Copy the code

Using the @data annotation automatically generates the following template code at compile time:

  • toString
  • equals
  • hashCode
  • Getters do not generate for final properties
  • Setters do not generate final properties
  • Constructor for the required parameters

What are the required parameters

All annotations

Having looked briefly at the @data annotation above, let’s take a look at all the annotations available

  • @nonNULL annotations on fields and constructor arguments. Add null null to setter, @requiredargsconstructor, @allargsconstructor; Annotations on constructor method parameters add nulling at construction time
  • Cleanup annotation on local variables. Is responsible for cleaning up resources, and the close method is called when the method ends directly
  • @setter annotation on a class or field. Annotations generate setter methods for all fields at class time and only for that field when they are on a field, and specify the access level of the generated setter methods
  • At sign Getter uses the same method as at sign Setter, the difference is that you generate a Getter
  • @toString annotations on the class. Add the toString method
  • @equalSandHashCode annotation in the class. Generate hashCode and equals methods
  • @noargsconstructor annotation in the class. Generates a constructor with no arguments.
  • RequiredArgsConstructor annotation in the class. Generate constructors for fields ina class that require special handling, such as final and fields annotated by @nonNULL.
  • The @allargsconstructor annotation generates a constructor containing all fields in the class.
  • The @data annotation generates setter/getter, equals, canEqual, hashCode, toString methods ina class. If it is a final property, it does not generate setter methods for that property.
  • @ValueAnnotations on classes and attributes. If the annotation is not modifiable on the class after the class instance is created, that is, setter methods are not generated, this causes@SetterDon’t work
  • The @Builder annotation on the class generates the constructor
  • @SneakyThrows
  • Synchronized annotation on methods, generate Synchronized methods
  • @With
  • Log related: annotation in class, generate log constant, similar to private static final XXX log
    • @Log java.util.logging.Logger
    • @CommonsLog org.apache.commons.logging.Log
    • @Flogger com.google.common.flogger.FluentLogger
    • @JBossLog org.jboss.logging.Logger
    • @Log4j org.apache.log4j.Logger
    • @Log4j2 org.apache.logging.log4j.Logger
    • @Slf4j org.slf4j.Logger
    • @XSlf4j org.slf4j.ext.XLogger

About all the annotations can view projectlombok.org/features/al…

Comprehensive instance

Integrated Example 1

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter                     / / generated getter
@AllArgsConstructor         // Generate all parameters
@RequiredArgsConstructor    // Generate a constructor for the necessary parameters
@ToString           / / generated toString
@EqualsAndHashCode  Generate equals and hashCode
@Builder            // Build a builder
public class UserLombok {

  // Create the setter and verify that the ID cannot be null
  @Setter
  @NonNull
  private Integer id;

  // Create setters and generate methods with access level PROTECTED
  @Setter(AccessLevel.PROTECTED)
  private Integer age;

  // Create setters that do not verify whether they are null
  @Setter
  private String realName;

  // Constructor, check id cannot be empty
  public UserLombok(@NonNull Integer id, Integer age) {
    this.id = id;
    this.age = age;
  }

  /** * Custom setter methods for realName, which takes precedence over Lombok *@paramRealName realName */
  public void setRealName(String realName) {
    this.realName = "realName:"+ realName; }}Copy the code

The generated class is

import lombok.NonNull;

public class UserLombok {
  @NonNull
  private Integer id;
  private Integer age;
  private String realName;

  public UserLombok(@NonNull Integer id, Integer age) {
    if (id == null) {
      throw new NullPointerException("id is marked non-null but is null");
    } else {
      this.id = id;
      this.age = age; }}public void setRealName(String realName) {
    this.realName = "realName:" + realName;
  }

  public static UserLombok.UserLombokBuilder builder(a) {
    return new UserLombok.UserLombokBuilder();
  }

  @NonNull
  public Integer getId(a) {
    return this.id;
  }

  public Integer getAge(a) {
    return this.age;
  }

  public String getRealName(a) {
    return this.realName;
  }

  public UserLombok(@NonNull Integer id, Integer age, String realName) {
    if (id == null) {
      throw new NullPointerException("id is marked non-null but is null");
    } else {
      this.id = id;
      this.age = age;
      this.realName = realName; }}public UserLombok(@NonNull Integer id) {
    if (id == null) {
      throw new NullPointerException("id is marked non-null but is null");
    } else {
      this.id = id; }}public String toString(a) {
    return "UserLombok(id=" + this.getId() + ", age=" + this.getAge() + ", realName=" + this.getRealName() + ")";
  }

  public boolean equals(Object o) {
    if (o == this) {
      return true;
    } else if(! (oinstanceof UserLombok)) {
      return false;
    } else {
      UserLombok other = (UserLombok)o;
      if(! other.canEqual(this)) {
        return false;
      } else {
        label47: {
          Object this$id = this.getId();
          Object other$id = other.getId();
          if (this$id == null) {
            if (other$id == null) {
              breaklabel47; }}else if (this$id.equals(other$id)) {
            break label47;
          }

          return false;
        }

        Object this$age = this.getAge();
        Object other$age = other.getAge();
        if (this$age == null) {
          if(other$age ! =null) {
            return false; }}else if (!this$age.equals(other$age)) {
          return false;
        }

        Object this$realName = this.getRealName();
        Object other$realName = other.getRealName();
        if (this$realName == null) {
          if(other$realName ! =null) {
            return false; }}else if (!this$realName.equals(other$realName)) {
          return false;
        }

        return true; }}}protected boolean canEqual(Object other) {
    return other instanceof UserLombok;
  }

  public int hashCode(a) {
    int PRIME = true;
    int result = 1;
    Object $id = this.getId();
    int result = result * 59 + ($id == null ? 43 : $id.hashCode());
    Object $age = this.getAge();
    result = result * 59 + ($age == null ? 43 : $age.hashCode());
    Object $realName = this.getRealName();
    result = result * 59 + ($realName == null ? 43 : $realName.hashCode());
    return result;
  }

  public void setId(@NonNull Integer id) {
    if (id == null) {
      throw new NullPointerException("id is marked non-null but is null");
    } else {
      this.id = id; }}protected void setAge(Integer age) {
    this.age = age;
  }

  public static class UserLombokBuilder {
    private Integer id;
    private Integer age;
    private String realName;

    UserLombokBuilder() {
    }

    public UserLombok.UserLombokBuilder id(@NonNull Integer id) {
      if (id == null) {
        throw new NullPointerException("id is marked non-null but is null");
      } else {
        this.id = id;
        return this; }}public UserLombok.UserLombokBuilder age(Integer age) {
      this.age = age;
      return this;
    }

    public UserLombok.UserLombokBuilder realName(String realName) {
      this.realName = realName;
      return this;
    }

    public UserLombok build(a) {
      return new UserLombok(this.id, this.age, this.realName);
    }

    public String toString(a) {
      return "UserLombok.UserLombokBuilder(id=" + this.id + ", age=" + this.age + ", realName=" + this.realName + ")"; }}}Copy the code

Integrated Example 2

@Value
public class UserLombok {

  @NonNull
  private Integer id;

  // The setter here will not be generated, so it doesn't work
  @Setter(AccessLevel.PROTECTED)
  private Integer age;

  private String realName;

}
Copy the code

The @Value annotation is a combination of ToString, EqualsAndHashCode, AllArgsConstructor, and Getter

Generated code

import lombok.NonNull;

public final class UserLombok {
  @NonNull
  private final Integer id;
  private final Integer age;
  private final String realName;

  public UserLombok(@NonNull Integer id, Integer age, String realName) {
    if (id == null) {
      throw new NullPointerException("id is marked non-null but is null");
    } else {
      this.id = id;
      this.age = age;
      this.realName = realName; }}@NonNull
  public Integer getId(a) {
    return this.id;
  }

  public Integer getAge(a) {
    return this.age;
  }

  public String getRealName(a) {
    return this.realName;
  }

  public boolean equals(Object o) {
    if (o == this) {
      return true;
    } else if(! (oinstanceof UserLombok)) {
      return false;
    } else {
      UserLombok other;
      label44: {
        other = (UserLombok)o;
        Object this$id = this.getId();
        Object other$id = other.getId();
        if (this$id == null) {
          if (other$id == null) {
            breaklabel44; }}else if (this$id.equals(other$id)) {
          break label44;
        }

        return false;
      }

      Object this$age = this.getAge();
      Object other$age = other.getAge();
      if (this$age == null) {
        if(other$age ! =null) {
          return false; }}else if (!this$age.equals(other$age)) {
        return false;
      }

      Object this$realName = this.getRealName();
      Object other$realName = other.getRealName();
      if (this$realName == null) {
        if(other$realName ! =null) {
          return false; }}else if (!this$realName.equals(other$realName)) {
        return false;
      }

      return true; }}public int hashCode(a) {
    int PRIME = true;
    int result = 1;
    Object $id = this.getId();
    int result = result * 59 + ($id == null ? 43 : $id.hashCode());
    Object $age = this.getAge();
    result = result * 59 + ($age == null ? 43 : $age.hashCode());
    Object $realName = this.getRealName();
    result = result * 59 + ($realName == null ? 43 : $realName.hashCode());
    return result;
  }

  public String toString(a) {
    return "UserLombok(id=" + this.getId() + ", age=" + this.getAge() + ", realName=" + this.getRealName() + ")"; }}Copy the code

Comprehensive Example 3

Log to use

import lombok.extern.java.Log;

@Log
public class LogLombok {

  public void log(a) {
    log.info("Keep a journal."); }}Copy the code

Generated code

import java.util.logging.Logger;

public class LogLombok {
  private static final Logger log = Logger.getLogger(LogLombok.class.getName());

  public LogLombok(a) {}public void log(a) {
    log.info("Keep a journal."); }}Copy the code

From the example above, we can see that Lombok can greatly simplify our code

Lombok’s pros and cons

Advantages:

  1. To improve the development efficiency, it automatically generates getters/setters, toString, Builder, etc. Especially in the process of changing classes. If we use the code automatically generated by IDEA, we need to constantly delete and regenerate it. Lombok automatically helps us to complete this
  2. To make the code concise, do not pay too much attention to the corresponding template methods, getters/setters, toString, Builder are template code, not to write, and Java 14 is already planned supportrecordAlso helps us solve this template code from a native perspective
  3. Property changes also simplify the maintenance of getter/setter methods generated for these properties

Disadvantages:

  1. Different developers working on the same Lombok project need to install the Lombok plug-in
  2. The corresponding setter, getter, Builder and IDEA cannot help automatic reconstruction
  3. It may reduce the readability and integrity of the source code, reducing the comfort of reading the source code, who would read the template code

Resolve compile-time errors

Error at compile time, possibly because annotation processor is not enabled. Build, Execution, Deployment > Annotation Processors > Enable Annotation processing. The program runs normally after setting.

Avoid pit guide

  • Try not to use@DataAnnotations, this annotation is too comprehensive to maintain unless you know what you’re doing, okay
  • The Java default mechanism does not generate a no-argument constructor if there are other constructors, in use@AllArgsConstructorWhen you annotate, be sure to add@NoArgsConstructor
  • This is not recommended if the class definition is in the change phase@AllArgsConstructorannotations
  • @Setter,@GetterAnnotations can be narrowed down if needed
  • @ToStringAnnotations do not generate parent information by default, if needed@ToString(callSuper = true)
  • @RequiredArgsConstructor@NoArgsConstructorTry not to use them together; the no-parameter constructor cannot handle them@NonNull, but in serialization/deserialization still need to provide no arguments
  • When the team decides to stop using Lombok, they can use the Delombok plugin to remove Lombok with one clickRefactor > Delombok

Note again that -@allargsconstructor should never be used

reference

  • projectlombok.org
  • Github.com/rzwitserloo…

How Lombok works

Working principle comes from online information

In Lombok’s use, annotations are added and no code is written. How exactly does auto-generated code come about?

The core is the interpretation of annotations. While JDK5 introduces annotations, it also provides two ways of parsing.

  • Runtime resolution

For annotations that can be parsed at RUNTIME, you must set @Retention to RUNTIME so that they can be retrieved by reflection. The java.lang.reflect Package provides an AnnotatedElement interface that defines methods for retrieving annotation information. Class, Constructor, Field, Method, Package, etc. Those of you who are familiar with reflection will be familiar with this approach.

  • Compile-time parsing

There are two mechanisms for compile-time parsing, described briefly:

1) Annotation Processing Tool

Apt is generated from JDK5, JDK7 has been marked expired, it is not recommended to use, JDK8 has been completely removed, since JDK6, you can use Pluggable Annotation Processing API to replace it, apt is replaced mainly for 2 reasons:

  • The API is all in com.sun.mirror non-standard package
  • It is not integrated into javAC and needs to be run extra

2) Pluggable Annotation Processing API

JSR 269 was added from JDK6 as an alternative to APT. It solves two problems with APT. Javac calls programs that implement this API when executing, so that we can make some enhancements to the compiler.

Lombok is essentially a program that implements the “JSR 269 API.” When using JavAC, it works as follows:

  1. Javac analyzes the source code to generate an abstract syntax tree (AST)
  2. A Lombok program that implements the “JSR 269 API” is invoked at run time
  3. Lombok then processes the AST obtained in the first step, finds the syntax tree (AST) corresponding to the class of the @data annotation, and modifies the AST to add the corresponding tree nodes defined by getter and setter methods
  4. Javac generates bytecode files using a modified abstract syntax tree (AST), which adds new nodes (code blocks) to the class

Reading Lombok’s source code, you can see that the corresponding annotation implementation is in HandleXXX, such as the @getter annotation implementation in handleget.handle (). There are other libraries that use this approach, such as Google Auto, Dagger, and so on.