A rare Lombok Learning Guide

Keywords: Java Lombok Apache log4j

I. Introduction to Lombok

Lombok It is a java development plug-in, which enables Java developers to eliminate tedious and tedious code in business engineering through some annotations defined by it, especially for simple java model objects (POJO s). After using Lombok plug-in in the development environment, Java developers can save a lot of time for repeated construction, such as methods such as hashCode and equals, and methods such as accessor and toString of various business object models. For these methods, it can automatically help us generate these methods during the compilation of source code, but it does not reduce the performance of the program like reflection.

II. Lombok installation

2.1 build tools

Gradle

Add lombok dependency in the build.gradle file:

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.10'
    annotationProcessor 'org.projectlombok:lombok:1.18.10'
}
Maven

Add lombok dependency in pom.xml file of Maven project:

<dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
        <scope>provided</scope>
</dependency>
Ant

Suppose lombok.jar already exists in the lib directory, then set the javac task:

<javac srcdir="src" destdir="build" source="1.8">
    <classpath location="lib/lombok.jar" />
</javac>

2.2 IDE

Because Lombok only generates code in the compilation phase, the source code annotated with Lombok will be highlighted with errors in the IDE. To solve this problem, install the plug-ins corresponding to the IDE. It is not expanded in detail here. For specific installation methods, please refer to Setting up Lombok with Eclipse and IntelliJ This article.

III. Lombok

Note: the Lombok version used in the following example is 1.18.10

3.1 @Getter and @Setter annotation

You can annotate any class or field with @ Getter or @ Setter, Lombok will automatically generate the default getter/setter method.

@Getter comment
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
  // If getter method is not public, access level can be set.
    lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
    AnyAnnotation[] onMethod() default {};
  // Enable delay initialization or not
    boolean lazy() default false;
}
@Setter comment
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Setter {
  // If the setter method is not public, the access level can be set.
    lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
    AnyAnnotation[] onMethod() default {};
    AnyAnnotation[] onParam() default {};
}
Use example
package com.semlinker.lombok;

@Getter
@Setter
public class GetterAndSetterDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class GetterAndSetterDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;

    public GetterAndSetterDemo() {
    }

    // Omit other setter and getter methods
    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}
Lazy Getter

@Getter annotation supports a lazy attribute, which is false by default. When set to true, delay initialization is enabled, that is, initialization occurs when the getter method is first called.

Example
package com.semlinker.lombok;

public class LazyGetterDemo {
    public static void main(String[] args) {
        LazyGetterDemo m = new LazyGetterDemo();
        System.out.println("Main instance is created");
        m.getLazy();
    }

    @Getter
    private final String notLazy = createValue("not lazy");

    @Getter(lazy = true)
    private final String lazy = createValue("lazy");

    private String createValue(String name) {
        System.out.println("createValue(" + name + ")");
        return null;
    }
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class LazyGetterDemo {
    private final String notLazy = this.createValue("not lazy");
    private final AtomicReference<Object> lazy = new AtomicReference();

    // Some codes have been omitted
    public String getNotLazy() {
        return this.notLazy;
    }

    public String getLazy() {
        Object value = this.lazy.get();
        if (value == null) {
            synchronized(this.lazy) {
                value = this.lazy.get();
                if (value == null) {
                    String actualValue = this.createValue("lazy");
                    value = actualValue == null ? this.lazy : actualValue;
                    this.lazy.set(value);
                }
            }
        }

        return (String)((String)(value == this.lazy ? null : value));
    }
}

It can be seen from the above code that when the getLazy method is called, if value is found to be null, the initialization operation will be performed in the synchronization code block.

3.2 Constructor Annotations

@NoArgsConstructor comments

The @ NoArgsConstructor annotation can be used to generate a default constructor for a specified class. The @ NoArgsConstructor annotation is defined as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {
  // Setting this property will generate a private constructor and a static method specified by staticName
    String staticName() default "";    
    AnyAnnotation[] onConstructor() default {};
  // Set the access level of the build constructor. The default is public
    AccessLevel access() default lombok.AccessLevel.PUBLIC;
  // If it is set to true, the field initializing all final is 0/null/false
    boolean force() default false;
}
Example
package com.semlinker.lombok;

@NoArgsConstructor(staticName = "getInstance")
public class NoArgsConstructorDemo {
    private long id;
    private String name;
    private int age;
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class NoArgsConstructorDemo {
    private long id;
    private String name;
    private int age;

    private NoArgsConstructorDemo() {
    }

    public static NoArgsConstructorDemo getInstance() {
        return new NoArgsConstructorDemo();
    }
}
@AllArgsConstructor comments

The @ AllArgsConstructor annotation can be used to generate a constructor containing all members for a specified class. The @ AllArgsConstructor annotation is defined as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface AllArgsConstructor {
  // Setting this property will generate a private constructor and a static method specified by staticName
    String staticName() default "";
    AnyAnnotation[] onConstructor() default {};
  // Set the access level of the build constructor. The default is public
    AccessLevel access() default lombok.AccessLevel.PUBLIC;
}
Example
package com.semlinker.lombok;

@AllArgsConstructor
public class AllArgsConstructorDemo {
    private long id;
    private String name;
    private int age;
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class AllArgsConstructorDemo {
    private long id;
    private String name;
    private int age;

    public AllArgsConstructorDemo(long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}
@RequiredArgsConstructorDemo annotation

The @ RequiredArgsConstructor annotation can be used to generate a corresponding constructor for the member variables that must be initialized for a specified class, such as the final member variable. The @ RequiredArgsConstructor annotation is defined as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface RequiredArgsConstructor {
  // Setting this property will generate a private constructor and a static method specified by staticName
    String staticName() default "";
    AnyAnnotation[] onConstructor() default {};
  // Set the access level of the build constructor. The default is public
    AccessLevel access() default lombok.AccessLevel.PUBLIC;
}
Example
package com.semlinker.lombok;

@RequiredArgsConstructor
public class RequiredArgsConstructorDemo {
    private final long id;
    private String name;
    private int age;
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class RequiredArgsConstructorDemo {
    private final long id;
    private String name;
    private int age;

    public RequiredArgsConstructorDemo(long id) {
        this.id = id;
    }
}

3.3 @EqualsAndHashCode annotation

The @ EqualsAndHashCode annotation can be used to generate equals and hashCode methods for a specified class. The @ EqualsAndHashCode annotation is defined as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface EqualsAndHashCode {
  // Specifies the list of fields to exclude in the generated equals and hashCode methods
    String[] exclude() default {};
    
  // Explicitly list the fields for identity. Generally, non static and non transient fields will be used for identity.
    String[] of() default {};
    
  // Identifies whether to call the equals and hashCode methods of the parent class before performing field calculation
    boolean callSuper() default false;
    
    boolean doNotUseGetters() default false;
    
    AnyAnnotation[] onParam() default {};
    
    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    @interface AnyAnnotation {}
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude {}
    
    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {
        String replaces() default "";
    }
}
Example
package com.semlinker.lombok;

@EqualsAndHashCode
public class EqualsAndHashCodeDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class EqualsAndHashCodeDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;

    public EqualsAndHashCodeDemo() {
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof EqualsAndHashCodeDemo)) {
            return false;
        } else {
            EqualsAndHashCodeDemo other = (EqualsAndHashCodeDemo)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
              // A lot of code has been omitted
        }
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $firstName = this.firstName;
        int result = result * 59 + ($firstName == null ? 43 : $firstName.hashCode());
        Object $lastName = this.lastName;
        result = result * 59 + ($lastName == null ? 43 : $lastName.hashCode());
        Object $dateOfBirth = this.dateOfBirth;
        result = result * 59 + ($dateOfBirth == null ? 43 : $dateOfBirth.hashCode());
        return result;
    }
}

3.4 @ToString annotation

The @ toString annotation can be used to generate a toString method for a specified class. The @ toString annotation is defined as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {
  // Whether the name of the field is included in the printout
    boolean includeFieldNames() default true;
    
  // List the fields to exclude when printing out
    String[] exclude() default {};
    
  // Explicitly list the fields to be printed out
    String[] of() default {};
    
  // Whether the printout result contains the return result of the toString method of the parent class
    boolean callSuper() default false;
    
    boolean doNotUseGetters() default false;
    
    boolean onlyExplicitlyIncluded() default false;
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude {}
    
    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {
        int rank() default 0;
        String name() default "";
    }
}
Example
package com.semlinker.lombok;

@ToString(exclude = {"dateOfBirth"})
public class ToStringDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class ToStringDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;

    public ToStringDemo() {
    }

    public String toString() {
        return "ToStringDemo(firstName=" + this.firstName + ", lastName=" + 
          this.lastName + ")";
    }
}

3.5 @Data annotation

@The Data annotation has the same effect as using the following annotations at the same time:

  • @ToString
  • @Getter
  • @Setter
  • @RequiredArgsConstructor
  • @EqualsAndHashCode

@The Data annotation is defined as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
    String staticConstructor() default "";
}
Example
package com.semlinker.lombok;

@Data
public class DataDemo {
    private Long id;
    private String summary;
    private String description;
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class DataDemo {
    private Long id;
    private String summary;
    private String description;

    public DataDemo() {
    }

    // Omit setter and getter methods for summary and description member properties
    public Long getId() {
        return this.id;
    }
  
    public void setId(Long id) {
        this.id = id;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof DataDemo)) {
            return false;
        } else {
            DataDemo other = (DataDemo)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
               // A lot of code has been omitted
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $id = this.getId();
        int result = result * 59 + ($id == null ? 43 : $id.hashCode());
        Object $summary = this.getSummary();
        result = result * 59 + ($summary == null ? 43 : $summary.hashCode());
        Object $description = this.getDescription();
        result = result * 59 + ($description == null ? 43 : $description.hashCode());
        return result;
    }

    public String toString() {
        return "DataDemo(id=" + this.getId() + ", summary=" + this.getSummary() + ", description=" + this.getDescription() + ")";
    }
}

3.6 @Log annotation

If you put the @ Log variant on a class (applicable to any of the logging systems you use); after that, you will have a static final log field, which you can then use to output logs.

@Log
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4j
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j
private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
@CommonsLog
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);

3.7 @Synchronized annotation

@Synchronized is a more secure variant of the synchronous method modifier. Like synchronized, this annotation can only be applied to static and instance methods. It operates like the synchronized keyword, but it is locked on a different object. When the synchronized keyword is applied to the instance method, it locks the this object, while it locks the class object on the static method. For the @ synchronized annotation declared method, it locks $lock or $lock. @The synchronized annotation is defined as follows:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Synchronized {
  // Specify the locked field name
    String value() default "";
}
Example
package com.semlinker.lombok;

public class SynchronizedDemo {
    private final Object readLock = new Object();

    @Synchronized
    public static void hello() {
        System.out.println("world");
    }

    @Synchronized
    public int answerToLife() {
        return 42;
    }

    @Synchronized("readLock")
    public void foo() {
        System.out.println("bar");
    }
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class SynchronizedDemo {
    private static final Object $LOCK = new Object[0];
    private final Object $lock = new Object[0];
    private final Object readLock = new Object();

    public SynchronizedDemo() {
    }

    public static void hello() {
        synchronized($LOCK) {
            System.out.println("world");
        }
    }

    public int answerToLife() {
        synchronized(this.$lock) {
            return 42;
        }
    }

    public void foo() {
        synchronized(this.readLock) {
            System.out.println("bar");
        }
    }
}

3.8 @Builder annotation

Using the @ Builder annotation can implement the Builder pattern for a specified class, which can be placed on a class, constructor, or method. @The Builder annotation is defined as follows:

@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
    @Target(FIELD)
    @Retention(SOURCE)
    public @interface Default {}

  // Method name for creating a new builder instance
    String builderMethodName() default "builder";
    // Create the method name of the corresponding instance of the Builder annotation class
    String buildMethodName() default "build";
    // The name of the builder class
    String builderClassName() default "";
    
    boolean toBuilder() default false;
    
    AccessLevel access() default lombok.AccessLevel.PUBLIC;
    
    @Target({FIELD, PARAMETER})
    @Retention(SOURCE)
    public @interface ObtainVia {
        String field() default "";
        String method() default "";
        boolean isStatic() default false;
    }
}
Example
package com.semlinker.lombok;

@Getter
@EqualsAndHashCode
@AllArgsConstructor
@Builder
public class BuilderDemo {
    private final String firstname;
    private final String lastname;
    private final String email;
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class BuilderDemo {
    private final String firstname;
    private final String lastname;
    private final String email;

    BuilderDemo(String firstname, String lastname, String email) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.email = email;
    }

    public static BuilderDemo.BuilderDemoBuilder builder() {
        return new BuilderDemo.BuilderDemoBuilder();
    }

    public static class BuilderDemoBuilder {
        private String firstname;
        private String lastname;
        private String email;

        BuilderDemoBuilder() {
        }

        public BuilderDemo.BuilderDemoBuilder firstname(String firstname) {
            this.firstname = firstname;
            return this;
        }

        public BuilderDemo.BuilderDemoBuilder lastname(String lastname) {
            this.lastname = lastname;
            return this;
        }

        public BuilderDemo.BuilderDemoBuilder email(String email) {
            this.email = email;
            return this;
        }

        public BuilderDemo build() {
            return new BuilderDemo(this.firstname, this.lastname, this.email);
        }

        public String toString() {
            return "BuilderDemo.BuilderDemoBuilder(firstname=" + this.firstname + ", lastname=" + this.lastname + ", email=" + this.email + ")";
        }
    }
}

3.9 @SneakyThrows annotation

@The SneakyThrows annotation is used to automatically throw checked exceptions without the need to explicitly throw them in a method using the throw statement. @The SneakyThrows annotation is defined as follows:

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface SneakyThrows {
    // Set the exception class you want to throw up
    Class<? extends Throwable>[] value() default java.lang.Throwable.class;
}
Example
package com.semlinker.lombok;

public class SneakyThrowsDemo {
    @SneakyThrows
    @Override
    protected Object clone() {
        return super.clone();
    }
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class SneakyThrowsDemo {
    public SneakyThrowsDemo() {
    }

    protected Object clone() {
        try {
            return super.clone();
        } catch (Throwable var2) {
            throw var2;
        }
    }
}

3.10 @NonNull annotation

You can use the @ NonNull annotation on the parameters of a method or constructor, which will automatically generate non null validation statements for you. @The NonNull annotation is defined as follows:

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE})
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface NonNull {
}
Example
package com.semlinker.lombok;

public class NonNullDemo {
    @Getter
    @Setter
    @NonNull
    private String name;
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class NonNullDemo {
    @NonNull
    private String name;

    public NonNullDemo() {
    }

    @NonNull
    public String getName() {
        return this.name;
    }

    public void setName(@NonNull String name) {
        if (name == null) {
            throw new NullPointerException("name is marked non-null but is null");
        } else {
            this.name = name;
        }
    }
}

3.11 @Clean annotation

@The Clean annotation is used to automatically manage resources. Before using it in local variables, it will automatically Clean up resources and automatically generate code such as try finally to close the flow before the execution and exit within the scope of current variables.

@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.SOURCE)
public @interface Cleanup {
  // Set the name of the method used to perform resource cleanup / recycling. The corresponding method cannot contain any parameters. The default name is close.
    String value() default "close";
}
Example
package com.semlinker.lombok;

public class CleanupDemo {
    public static void main(String[] args) throws IOException {
        @Cleanup InputStream in = new FileInputStream(args[0]);
        @Cleanup OutputStream out = new FileOutputStream(args[1]);
        byte[] b = new byte[10000];
        while (true) {
            int r = in.read(b);
            if (r == -1) break;
            out.write(b, 0, r);
        }
    }
}

After Lombok compiles the above code, the following code will be generated:

package com.semlinker.lombok;

public class CleanupDemo {
    public CleanupDemo() {
    }

    public static void main(String[] args) throws IOException {
        FileInputStream in = new FileInputStream(args[0]);

        try {
            FileOutputStream out = new FileOutputStream(args[1]);

            try {
                byte[] b = new byte[10000];

                while(true) {
                    int r = in.read(b);
                    if (r == -1) {
                        return;
                    }

                    out.write(b, 0, r);
                }
            } finally {
                if (Collections.singletonList(out).get(0) != null) {
                    out.close();
                }

            }
        } finally {
            if (Collections.singletonList(in).get(0) != null) {
                in.close();
            }
        }
    }
}

3.11 @With annotation

After the @ With annotation is applied to the field of the class, a method With fieldname (newValue) will be generated automatically. The method will call the corresponding constructor based on newValue to create an instance corresponding to the current class. @The With annotation is defined as follows:

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface With {
    AccessLevel value() default AccessLevel.PUBLIC;

    With.AnyAnnotation[] onMethod() default {};

    With.AnyAnnotation[] onParam() default {};

    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    public @interface AnyAnnotation {
    }
}
Example
public class WithDemo {
    @With(AccessLevel.PROTECTED)
    @NonNull
    private final String name;
    @With
    private final int age;

    public WithDemo(String name, int age) {
        if (name == null) throw new NullPointerException();
        this.name = name;
        this.age = age;
    }
}

After Lombok compiles the above code, the following code will be generated:

public class WithDemo {
    @NonNull
    private final String name;
    private final int age;

    public WithDemo(String name, int age) {
        if (name == null) {
            throw new NullPointerException();
        } else {
            this.name = name;
            this.age = age;
        }
    }

    protected WithDemo withName(@NonNull String name) {
        if (name == null) {
            throw new NullPointerException("name is marked non-null but is null");
        } else {
            return this.name == name ? this : new WithDemo(name, this.age);
        }
    }

    public WithDemo withAge(int age) {
        return this.age == age ? this : new WithDemo(this.name, age);
    }
}

3.12 other characteristics

val

val is used in front of local variables, which is equivalent to declaring variables as final. In addition, Lombok automatically performs type inference at compile time. Use example of val:

public class ValExample {
  public String example() {
    val example = new ArrayList<String>();
    example.add("Hello, World!");
    val foo = example.get(0);
    return foo.toLowerCase();
  }
  
  public void example2() {
    val map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (val entry : map.entrySet()) {
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}

The above code is equivalent to:

public class ValExample {
  public String example() {
    final ArrayList<String> example = new ArrayList<String>();
    example.add("Hello, World!");
    final String foo = example.get(0);
    return foo.toLowerCase();
  }
  
  public void example2() {
    final HashMap<Integer, String> map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (final Map.Entry<Integer, String> entry : map.entrySet()) {
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}

At this point, the powerful Lombok tool is introduced. If you are interested in its implementation principle, it is recommended to read the code of apes. Ten minutes to understand the use and principle of Lombok This article.

Example project address: Github - springboot2-lombok

IV. reference resources

Posted by The_Stranger on Wed, 30 Oct 2019 13:07:16 -0700