在面向?qū)ο缶幊讨斜夭豢缮傩枰诖a中定義對象模型,而在基于Java的業(yè)務(wù)平臺開發(fā)實(shí)踐中尤其如此。相信大家在平時開發(fā)中也深有感觸,本來是沒有多少代碼開發(fā)量的,但是因?yàn)槎x的業(yè)務(wù)模型對象比較多,而需要重復(fù)寫Getter/Setter、構(gòu)造器方法、字符串輸出的ToString方法和Equals/HashCode方法等。那么是否一款插件或工具能夠替大家完成這些繁瑣的操作呢?本文將向大家介紹一款在Eclipse/Intellij IDEA主流的開發(fā)環(huán)境中都可以使用的Java開發(fā)神器,同時簡要地介紹下其背后自定義注解的原理。
Lombok的簡介
Lombok是一款Java開發(fā)插件,使得Java開發(fā)者可以通過其定義的一些注解來消除業(yè)務(wù)工程中冗長和繁瑣的代碼,尤其對于簡單的Java模型對象(POJO)。在開發(fā)環(huán)境中使用Lombok插件后,Java開發(fā)人員可以節(jié)省出重復(fù)構(gòu)建,諸如hashCode和equals這樣的方法以及各種業(yè)務(wù)對象模型的accessor和ToString等方法的大量時間。對于這些方法,它能夠在編譯源代碼期間自動幫我們生成這些方法,并沒有如反射那樣降低程序的性能。
在Intellij中安裝Lombok的插件
想要體驗(yàn)一把Lombok的話,得先在自己的開發(fā)環(huán)境中安裝上對應(yīng)的插件。下面先為大家展示下如何在Intellij中安裝上Lombok插件。
通過IntelliJ的插件中心尋找Lombok
從Intellij插件中心安裝Lombok
另外需要注意的是,在使用lombok注解的時候記得要導(dǎo)入lombok.jar包到工程,如果使用的是Maven的工程項(xiàng)目的話,要在其pom.xml中添加依賴如下:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
</dependency>
好了,就這么幾步后就可以在Java工程中開始用Lombok這款開發(fā)神器了。下文將會給大家介紹Lombok中一些注解的使用方法,讓大家對如何用這些注解有一個大致的了解。
Lombok注解使用方法
Lombok常用注解介紹
下面先來看下Lombok中主要幾個常用注解介紹:
Lombok的基本使用示例
(1) Val可以將變量申明是final類型。
public static void main(String[] args) {
val setVar = new HashSet<String>();
val listsVar = new ArrayList<String>();
val mapVar = new HashMap<String, String>();
//=>上面代碼相當(dāng)于如下:
final Set<String> setVar2 = new HashSet<>();
final List<String> listsVar2 = new ArrayList<>();
final Map<String, String> maps2 = new HashMap<>();
}
(2) @NonNull注解能夠?yàn)榉椒ɑ驑?gòu)造函數(shù)的參數(shù)提供非空檢查。
public void notNullExample(@NonNull String string) {
string.length();
}
//=>相當(dāng)于
public void notNullExample(String string) {
if (string != null) {
string.length();
} else {
throw new NullPointerException("null");
}
}
(3) @Cleanup注解能夠自動釋放資源。

(4) @Getter/@Setter注解可以針對類的屬性字段自動生成Get/Set方法。

(5) @ToString注解,為使用該注解的類生成一個toString方法,默認(rèn)的toString格式為:ClassName(fieldName= fieleValue ,fieldName1=fieleValue)。
@ToString(callSuper=true,exclude="someExcludedField")
public class Demo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
}
//上面代碼相當(dāng)于如下:
public class Demo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
@ Override
public String toString() {
return "Foo(super=" + super.toString() +
", someBoolean=" + someBoolean +
", someStringField=" + someStringField + ")";
}
}
(6) @EqualsAndHashCode注解,為使用該注解的類自動生成equals和hashCode方法。

(7) @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor,這幾個注解分別為類自動生成了無參構(gòu)造器、指定參數(shù)的構(gòu)造器和包含所有參數(shù)的構(gòu)造器。
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
private int x, y;
@NonNull private T description;
@NoArgsConstructor
public static class NoArgsExample {
@NonNull private String field;
}
}
//上面代碼相當(dāng)于如下:
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
private int x, y;
@NonNull private T description;
@NoArgsConstructor
public static class NoArgsExample {
@NonNull private String field;
}
}
public class ConstructorExample<T> {
private int x, y;
@NonNull private T description;
private ConstructorExample(T description) {
if (description == null) throw new NullPointerException("description");
this.description = description;
}
public static <T> ConstructorExample<T> of(T description) {
return new ConstructorExample<T>(description);
}
@java.beans.ConstructorProperties({"x", "y", "description"})
protected ConstructorExample(int x, int y, T description) {
if (description == null) throw new NullPointerException("description");
this.x = x;
this.y = y;
this.description = description;
}
public static class NoArgsExample {
@NonNull private String field;
public NoArgsExample() {
}
}
}
(8) @Data注解作用比較全,其包含注解的集合 @ToString, @EqualsAndHashCode,所有字段的 @Getter和所有非final字段的 @Setter, @RequiredArgsConstructor。其示例代碼可以參考上面幾個注解的組合。
(9) @Builder注解提供了一種比較推崇的構(gòu)建值對象的方式。
(10) @Synchronized注解類似Java中的Synchronized 關(guān)鍵字,但是可以隱藏同步鎖。
public class SynchronizedExample {
private final Object readLock = new Object();
@Synchronized
public static void hello() {
System.out.println("world");
}
@Synchronized("readLock")
public void foo() {
System.out.println("bar");
}
//上面代碼相當(dāng)于如下:
public class SynchronizedExample {
private static final Object $LOCK = new Object[0];
private final Object readLock = new Object();
public static void hello() {
synchronized($LOCK) {
System.out.println("world");
}
}
public void foo() {
synchronized(readLock) {
System.out.println("bar");
}
}
}
Lombok背后的自定義注解原理
本文在前三章節(jié)主要介紹了Lombok這款Java開發(fā)利器中各種定義注解的使用方法,但作為一個Java開發(fā)者來說光了解插件或者技術(shù)框架的用法只是做到了“知其然而不知其所以然”,如果真正掌握其背后的技術(shù)原理,看明白源碼設(shè)計理念才能真正做到“知其然知其所以然”。好了,話不多說下面進(jìn)入本章節(jié)的正題,看下Lombok背后注解的深入原理。
可能熟悉Java自定義注解的同學(xué)已經(jīng)猜到,Lombok這款插件正是依靠可插件化的Java自定義注解處理API(JSR 269: Pluggable Annotation Processing API)來實(shí)現(xiàn)在Javac編譯階段利用“Annotation Processor”對自定義的注解進(jìn)行預(yù)處理后生成真正在JVM上面執(zhí)行的“Class文件”。有興趣的同學(xué)反編譯帶有Lombok注解的類文件也就一目了然了。其大致執(zhí)行原理圖如下:
從上面的這個原理圖上可以看出Annotation Processing是編譯器在解析Java源代碼和生成Class文件之間的一個步驟。其中Lombok插件具體的執(zhí)行流程如下:
從上面的Lombok執(zhí)行的流程圖中可以看出,在Javac 解析成AST抽象語法樹之后, Lombok 根據(jù)自己編寫的注解處理器,動態(tài)地修改 AST,增加新的節(jié)點(diǎn)(即Lombok自定義注解所需要生成的代碼),最終通過分析生成JVM可執(zhí)行的字節(jié)碼Class文件。使用Annotation Processing自定義注解是在編譯階段進(jìn)行修改,而JDK的反射技術(shù)是在運(yùn)行時動態(tài)修改,兩者相比,反射雖然更加靈活一些但是帶來的性能損耗更加大。
需要更加深入理解Lombok插件的細(xì)節(jié),自己查閱其源代碼是必比可少的。對開源框架代碼比較有執(zhí)著追求的童鞋可以將Lombok的源代碼工程從github上download到本地進(jìn)行閱讀和自己調(diào)試。下圖為Lombok工程源代碼的截圖:
從熟悉JSR 269: Pluggable Annotation Processing API的同學(xué)可以從工程類結(jié)構(gòu)圖中發(fā)現(xiàn)AnnotationProcessor這個類是Lombok自定義注解處理的入口。該類有兩個比較重要的方法一個是init方法,另外一個是process方法。在init方法中,先用來做參數(shù)的初始化,將AnnotationProcessor類中定義的內(nèi)部類(JavacDescriptor、EcjDescriptor)先注冊到ProcessorDescriptor類型定義的列表中。其中,內(nèi)部靜態(tài)類—JavacDescriptor在其加載的時候就將 lombok.javac.apt.LombokProcessor這個類進(jìn)行對象實(shí)例化并注冊。
在 LombokProcessor處理器中,其中的process方法會根據(jù)優(yōu)先級來分別運(yùn)行相應(yīng)的handler處理類。Lombok中的多個自定義注解都分別有對應(yīng)的handler處理類,如下圖所示:
可以看出,在Lombok中對于其自定義注解進(jìn)行實(shí)際的替換、修改和處理的正是這些handler類。對于其實(shí)現(xiàn)的細(xì)節(jié)可以具體參考其中的代碼。
本文先從Lombok使用角度出發(fā),先介紹了如何在當(dāng)前主流的Java開發(fā)環(huán)境—Intellij中安裝這塊Java插件,隨后分別介紹了Lombok中幾種主要的常用注解(比如, @Data、 @CleanUp、 @ToString和 @Getter/Setter等),最后將Lombok背后Java自定義注解的原理與源代碼結(jié)合起來,介紹了Lombok插件背后具體的執(zhí)行處理流程。限于篇幅,未能對“自定義Java注解處理器”的具體實(shí)踐和原理進(jìn)行闡述,后面將另起專題篇幅進(jìn)行介紹。限于筆者的才疏學(xué)淺,對本文內(nèi)容可能還有理解不到位的地方,如有闡述不合理之處還望留言一起探討。