在面向對象編程中必不可少需要在代碼中定義對象模型,而在基於Java的業務平台開發實踐中尤其如此。相信大家在平時開發中也深有感觸,本來是沒有多少代碼開發量的,但是因為定義的業務模型對象比較多,而需要重復寫Getter/Setter、構造器方法、字符串輸出的ToString方法和Equals/HashCode方法等。那么是否一款插件或工具能夠替大家完成這些繁瑣的操作呢?本文將向大家介紹一款在Eclipse/Intellij IDEA主流的開發環境中都可以使用的Java開發神器,同時簡要地介紹下其背后自定義注解的原理。
Lombok的簡介
Lombok是一款Java開發插件,使得Java開發者可以通過其定義的一些注解來消除業務工程中冗長和繁瑣的代碼,尤其對於簡單的Java模型對象(POJO)。在開發環境中使用Lombok插件后,Java開發人員可以節省出重復構建,諸如hashCode和equals這樣的方法以及各種業務對象模型的accessor和ToString等方法的大量時間。對於這些方法,它能夠在編譯源代碼期間自動幫我們生成這些方法,並沒有如反射那樣降低程序的性能。
在Intellij中安裝Lombok的插件
想要體驗一把Lombok的話,得先在自己的開發環境中安裝上對應的插件。下面先為大家展示下如何在Intellij中安裝上Lombok插件。
通過IntelliJ的插件中心尋找Lombok

從Intellij插件中心安裝Lombok

另外需要注意的是,在使用lombok注解的時候記得要導入lombok.jar包到工程,如果使用的是Maven的工程項目的話,要在其pom.xml中添加依賴如下:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
</dependency>
好了,就這么幾步后就可以在Java工程中開始用Lombok這款開發神器了。下文將會給大家介紹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>(); //=>上面代碼相當於如下: final Set<String> setVar2 = new HashSet<>(); final List<String> listsVar2 = new ArrayList<>(); final Map<String, String> maps2 = new HashMap<>(); }
(2)@NonNull注解能夠為方法或構造函數的參數提供非空檢查。
public void notNullExample(@NonNull String string) { //方法內的代碼 } //=>上面代碼相當於如下: public void notNullExample(String string) { if (string != null) { //方法內的代碼相當於如下: } else { throw new NullPointerException("null"); } }
(3)@Cleanup注解能夠自動釋放資源。
public void jedisExample(String[] args) { try { @Cleanup Jedis jedis = redisService.getJedis(); } catch (Exception ex) { logger.error(“Jedis異常:”,ex) } //=>上面代碼相當於如下: Jedis jedis= null; try { jedis = redisService.getJedis(); } catch (Exception e) { logger.error(“Jedis異常:”,ex) } finally { if (jedis != null) { try { jedis.close(); } catch (Exception e) { e.printStackTrace(); } } } }
(4)@Getter/@Setter注解可以針對類的屬性字段自動生成Get/Set方法。
public class OrderCreateDemoReq{ @Getter @Setter private String customerId; @Setter @Getter private String poolId; //其他代碼…… } //上面請求Req類的代碼相當於如下: public class OrderCreateDemoReq{ private String customerId; private String poolId; public String getCustomerId(){ return customerId; } public String getPoolId(){ return poolId; } public void setCustomerId(String customerId){ this.customerId = customerId; } public void setPoolId(String poolId){ this.pool = pool; } }
(5)@ToString注解,為使用該注解的類生成一個toString方法,默認的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; } //上面代碼相當於如下: 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方法。
@EqualsAndHashCode(exclude = {"id"}, callSuper =true)
public class LombokDemo extends Demo{
private int id;
private String name;
private String gender;
}
//上面代碼相當於如下:
public class LombokDemo extends Demo{
private int id;
private String name;
private String gender;
@Override
public boolean equals(final Object o) {
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != this.getClass()) return false;
if (!super.equals(o)) return false;
final LombokDemo other = (LombokDemo)o;
if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false;
return true;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = result * PRIME + super.hashCode();
result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode());
return result;
}
}
(7) @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor,這幾個注解分別為類自動生成了無參構造器、指定參數的構造器和包含所有參數的構造器。
@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; } } //上面代碼相當於如下: @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注解提供了一種比較推崇的構建值對象的方式。
@Builder public class BuilderExample { private String name; private int age; @Singular private Set<String> occupations; } //上面代碼相當於如下: public class BuilderExample { private String name; private int age; private Set<String> occupations; BuilderExample(String name, int age, Set<String> occupations) { this.name = name; this.age = age; this.occupations = occupations; } public static BuilderExampleBuilder builder() { return new BuilderExampleBuilder(); } public static class BuilderExampleBuilder { private String name; private int age; private java.util.ArrayList<String> occupations; BuilderExampleBuilder() { } public BuilderExampleBuilder name(String name) { this.name = name; return this; } public BuilderExampleBuilder age(int age) { this.age = age; return this; } public BuilderExampleBuilder occupation(String occupation) { if (this.occupations == null) { this.occupations = new java.util.ArrayList<String>(); } this.occupations.add(occupation); return this; } public BuilderExampleBuilder occupations(Collection<? extends String> occupations) { if (this.occupations == null) { this.occupations = new java.util.ArrayList<String>(); } this.occupations.addAll(occupations); return this; } public BuilderExampleBuilder clearOccupations() { if (this.occupations != null) { this.occupations.clear(); } return this; } public BuilderExample build() { Set<String> occupations = new HashSet<>(); return new BuilderExample(name, age, occupations); } @verride public String toString() { return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")"; } } }
(10)@Synchronized注解類似Java中的Synchronized 關鍵字,但是可以隱藏同步鎖。
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"); } //上面代碼相當於如下: 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背后的自定義注解原理
本文在前三章節主要介紹了Lombok這款Java開發利器中各種定義注解的使用方法,但作為一個Java開發者來說光了解插件或者技術框架的用法只是做到了“知其然而不知其所以然”,如果真正掌握其背后的技術原理,看明白源碼設計理念才能真正做到“知其然知其所以然”。好了,話不多說下面進入本章節的正題,看下Lombok背后注解的深入原理。
可能熟悉Java自定義注解的同學已經猜到,Lombok這款插件正是依靠可插件化的Java自定義注解處理API(JSR 269: Pluggable Annotation Processing API)來實現在Javac編譯階段利用“Annotation Processor”對自定義的注解進行預處理后生成真正在JVM上面執行的“Class文件”。有興趣的同學反編譯帶有Lombok注解的類文件也就一目了然了。其大致執行原理圖如下:

從上面的這個原理圖上可以看出Annotation Processing是編譯器在解析Java源代碼和生成Class文件之間的一個步驟。其中Lombok插件具體的執行流程如下:

從上面的Lombok執行的流程圖中可以看出,在Javac 解析成AST抽象語法樹之后, Lombok 根據自己編寫的注解處理器,動態地修改 AST,增加新的節點(即Lombok自定義注解所需要生成的代碼),最終通過分析生成JVM可執行的字節碼Class文件。使用Annotation Processing自定義注解是在編譯階段進行修改,而JDK的反射技術是在運行時動態修改,兩者相比,反射雖然更加靈活一些但是帶來的性能損耗更加大。
需要更加深入理解Lombok插件的細節,自己查閱其源代碼是必比可少的。對開源框架代碼比較有執着追求的童鞋可以將Lombok的源代碼工程從github上download到本地進行閱讀和自己調試。下圖為Lombok工程源代碼的截圖:

從熟悉JSR 269: Pluggable Annotation Processing API的同學可以從工程類結構圖中發現AnnotationProcessor這個類是Lombok自定義注解處理的入口。該類有兩個比較重要的方法一個是init方法,另外一個是process方法。在init方法中,先用來做參數的初始化,將AnnotationProcessor類中定義的內部類(JavacDescriptor、EcjDescriptor)先注冊到ProcessorDescriptor類型定義的列表中。其中,內部靜態類—JavacDescriptor在其加載的時候就將lombok.javac.apt.LombokProcessor這個類進行對象實例化並注冊。
在LombokProcessor處理器中,其中的process方法會根據優先級來分別運行相應的handler處理類。Lombok中的多個自定義注解都分別有對應的handler處理類,如下圖所示:

可以看出,在Lombok中對於其自定義注解進行實際的替換、修改和處理的正是這些handler類。對於其實現的細節可以具體參考其中的代碼。
本文先從Lombok使用角度出發,先介紹了如何在當前主流的Java開發環境—Intellij中安裝這塊Java插件,隨后分別介紹了Lombok中幾種主要的常用注解(比如,@Data、@CleanUp、@ToString和@Getter/Setter等),最后將Lombok背后Java自定義注解的原理與源代碼結合起來,介紹了Lombok插件背后具體的執行處理流程。限於篇幅,未能對“自定義Java注解處理器”的具體實踐和原理進行闡述,后面將另起專題篇幅進行介紹。限於筆者的才疏學淺,對本文內容可能還有理解不到位的地方,如有闡述不合理之處還望留言一起探討。
本文作者:胡宗棠
原文鏈接:https://mp.weixin.qq.com/s/lAC0rvUG24e1O8xhq5mGQA
版權歸作者所有,轉載請注明出處
