天天看點

【好用的工具】lombok 使用指南

1. Lombok 是什麼?

Lombok 是一種 Java 實用工具,可用來幫助開發人員消除 Java 的冗長,尤其是對于簡單的 Java 對象(POJO)。它通過注釋實作這一目的。通過在開發環境中實作  Lombok,開發人員可以節省建構諸如 hashCode() 和 equals() 這樣的方法以及以往用來分類各種 accessor 和 mutator 的大量時間。

  • 倉庫位址:https://gitee.com/mirrors/lombok
  • 官網文檔:https://projectlombok.org/features/experimental/Accessors

1.1 注解說明

  • @NonNull 注解在字段和構造器的參數上。注解在字段上,則在 setter, constructor 方法中加入判空,注意這裡需要配合 @Setter、@RequiredArgsConstructor、@AllArgsConstructor 使用;注解在構造器方法參數上,則在構造的時候加入判空
  • @Cleanup 注解在本地變量上。負責清理資源,在目前變量範圍内即将執行完畢退出之前會自動清理資源,自動生成try-finally這樣的代碼來關閉流
  • @Setter 注解在類或字段。注解在類時為所有字段生成setter方法,注解在字段上時隻為該字段生成setter方法,同時可以指定生成的 setter 方法的通路級别
  • @Getter 使用方法同 @Setter,差別在于生成的是 getter 方法
  • @ToString 注解在類上。添加toString方法
  • @EqualsAndHashCode 注解在類。生成hashCode和equals方法
  • @NoArgsConstructor 注解在類。生成無參的構造方法。
  • @RequiredArgsConstructor 注解在類。為類中需要特殊處理的字段生成構造方法,比如 final 和被 @NonNull 注解的字段。
  • @AllArgsConstructor 注解在類,生成包含類中所有字段的構造方法。
  • @Data 注解在類,生成setter/getter、equals、canEqual、hashCode、toString方法,如為final屬性,則不會為該屬性生成setter方法。
  • @Value 注解在類和屬性上。如果注解在類上在類執行個體建立後不可修改,即不會生成 setter 方法,這個會導緻

    @Setter

    不起作用
  • @Builder 自動生成構造器,用在類、構造器、方法上,為你提供複雜的builder APIs,讓你可以像如下方式一樣調用

    Person.builder().name("Adam Savage").city("San 

  • @SneakyThrows:  注解用于自動抛出已檢查的異常,而無需在方法中使用 throw 語句顯式抛出
  • @Synchronized 注解在方法上,自動為被标記的方法添加synchronized鎖
  • @With:在類的字段上應用 

    @With

     注解之後,将會自動生成一個 

    withFieldName(newValue)

     的方法,該方法會基于 newValue 調用相應構造函數,建立一個目前類對應的執行個體
  • @Delegate:為标記屬性生成委托方法
  • @Accessors: 中文含義是存取器,@Accessors用于配置getter和setter方法的生成結果

日志相關: 注解在類,生成 log 常量,類似 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

關于所有的注解可以檢視  

舉例說明:

@Accessors: 中文含義是存取器,@Accessors用于配置getter和setter方法的生成結果

  • fluent:中文含義是流暢的,設定為true,則getter和setter方法的方法名都是基礎屬性名,且setter方法傳回目前對象
@Data
@Accessors(fluent = true)
public class User {
    private Long id;
    private String name;
    
    // 生成的getter和setter方法如下,方法體略
    public Long id() {}
    public User id(Long id) {}
    public String name() {}
    public User name(String name) {}
}
           
  • chain的中文含義是鍊式的,設定為true,則setter方法傳回目前對象。
@Data
@Accessors(chain = true)
public class User {
    private Long id;
    private String name;
    
    // 生成的setter方法如下,方法體略
    public User setId(Long id) {}
    public User setName(String name) {}
}


// 這樣使用的時候就可以 使用 user.setId(11L).setName("張三"); 這種形式了
           
  • prefix的中文含義是字首,用于生成getter和setter方法的字段名會忽視指定字首(遵守駝峰命名)。
@Data
@Accessors(prefix = "p")
class User {
	private Long pId;
	private String pName;
	
	// 生成的getter和setter方法如下,方法體略
	public Long getId() {}
	public void setId(Long id) {}
	public String getName() {}
	public void setName(String name) {}
}
           

@Builder 用在類、構造器、方法上,為你提供複雜的builder APIs,讓你可以像如下方式一樣調用

Person.builder().name("Adam Savage").city("San

@Builder
public class Person {
    private String name;
    private Integer age;
}


對應位元組碼:

public class Person {
    private String name;
    private Integer age;

    Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public static Person.PersonBuilder builder() {
        return new Person.PersonBuilder();
    }

    public static class PersonBuilder {
        private String name;
        private Integer age;

        PersonBuilder() {
        }

        public Person.PersonBuilder name(String name) {
            this.name = name;
            return this;
        }

        public Person.PersonBuilder age(Integer age) {
            this.age = age;
            return this;
        }

        public Person build() {
            return new Person(this.name, this.age);
        }

        public String toString() {
            return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ")";
        }
    }
}
           

@Delegate:為标記屬性生成委托方法

public class DelegateExample {
    public void show() {
        System.out.println("show...");
    }
}
@AllArgsConstructor
public class Demo {
    @Delegate
    private final DelegateExample delegateExample;
}

// 對應位元組碼

public class DelegateExample {
    public DelegateExample() {
    }

    public void show() {
        System.out.println("show...");
    }
}
public class Demo {
    private final DelegateExample delegateExample;

    public Demo(DelegateExample delegateExample) {
        this.delegateExample = delegateExample;
    }

    // 委托方法
    public void show() {
        this.delegateExample.show();
    }
}

           

@Synchronized: 自動為被标記的方法添加synchronized鎖

public class SynchronizedExample {
  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");
  }
}

對應的位元組碼檔案

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

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

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

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

2.怎麼使用 Lombok ?

使用Lombok的兩點主要原因就是:

  1. 簡化備援的JavaBean代碼;
  2. 大大提高JavaBean中方法的執行效率;

2.1 引入Maven 依賴

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

注意: 在這裡 

scope

 要設定為 

provided

, 防止依賴傳遞給其他項目

  • provided(已提供範圍):表示部署的環境當中有某容器已經提供了該jar包,隻在編譯classpath(不是運作時)可用。它們不是傳遞性的,也不會被打包。例如,如果你開發了一個web應用,你可能在編譯classpath中需要可用的Servlet API來編譯一個servlet,但是你不會想要在打包好的WAR中包含這個Servlet API;這個Servlet API JAR由你的servlet容器(Tomcat)提供。

2.2 安裝插件(可選)

在開發過程中,一般還需要配合插件使用,在 IDEA 中需要安裝

Lombok

插件即可

為什麼要安裝插件?

首先在不安裝插件的情況下,代碼是可以正常的編譯和運作的。如果不安裝插件,IDEA 不會自動提示 Lombok 在編譯時才會生成的一些樣闆方法,同樣 IDEA 在校驗文法正确性的時候也會提示有問題,會有大面積報紅的代碼

2.3 對比使用lombok有什麼不一樣

不使用 Lombok

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

  public User() {
  }

  @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() {
    int result = 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() {
    return "User{" +
        "id=" + id +
        ", age=" + age +
        ", realName='" + realName + '\'' +
        '}';
  }

  public Integer getId() {
    return id;
  }

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

  public Integer getAge() {
    return age;
  }

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

  public String getRealName() {
    return realName;
  }

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

使用Lombok

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

使用

@Data

注解會在編譯的時候自動生成以下模闆代碼:

  • toString
  • equals
  • hashCode
  • getter 不會對 final 屬性生成
  • setter 不會對 final 屬性生成
  • 必要參數的構造器

3. lombok 原理

說道 Lombok,我們就得去提到 JSR 269: Pluggable Annotation Processing API (www.jcp.org/en/jsr/deta…) 。JSR 269 之前我們也有注解這樣的神器,可是我們比如想要做什麼必須使用反射,反射的方法局限性較大。首先,它必須定義@Retention為RetentionPolicy.RUNTIME,隻能在運作時通過反射來擷取注解值,使得運作時代碼效率降低。其次,如果想在編譯階段利用注解來進行一些檢查,對使用者的某些不合理代碼給出錯誤報告,反射的使用方法就無能為力了。而 JSR 269 之後我們可以在 Javac的編譯期利用注解做這些事情。是以我們發現核心的區分是在 運作期 還是 編譯期。

【好用的工具】lombok 使用指南

從上圖可知,Annotation Processing 是在解析和生成之間的一個步驟。具體詳細步驟如下:

【好用的工具】lombok 使用指南

上圖是 Lombok 處理流程,在Javac 解析成抽象文法樹之後(AST), Lombok 根據自己的注解處理器,動态的修改 AST,增加新的節點(所謂代碼),最終通過分析和生成位元組碼。