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
的方法,該方法會基于 newValue 調用相應構造函數,建立一個目前類對應的執行個體withFieldName(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的兩點主要原因就是:
- 簡化備援的JavaBean代碼;
- 大大提高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的編譯期利用注解做這些事情。是以我們發現核心的區分是在 運作期 還是 編譯期。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yYlNmMmVDNjZmYwYjN2YTYyIGMmRmN3MmMmRTM2QjMi9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
從上圖可知,Annotation Processing 是在解析和生成之間的一個步驟。具體詳細步驟如下:
上圖是 Lombok 處理流程,在Javac 解析成抽象文法樹之後(AST), Lombok 根據自己的注解處理器,動态的修改 AST,增加新的節點(所謂代碼),最終通過分析和生成位元組碼。