Java開發(fā)神器Lombok

在面向?qū)ο缶幊讨斜夭豢缮傩枰诖a中定義對(duì)象模型蜘澜,而在基于Java的業(yè)務(wù)平臺(tái)開發(fā)實(shí)踐中尤其如此复旬。相信大家在平時(shí)開發(fā)中也深有感觸,本來是沒有多少代碼開發(fā)量的步势,但是因?yàn)槎x的業(yè)務(wù)模型對(duì)象比較多氧猬,而需要重復(fù)寫Getter/Setter、構(gòu)造器方法坏瘩、字符串輸出的ToString方法和Equals/HashCode方法等盅抚。那么是否一款插件或工具能夠替大家完成這些繁瑣的操作呢?本文將向大家介紹一款在Eclipse/Intellij IDEA主流的開發(fā)環(huán)境中都可以使用的Java開發(fā)神器倔矾,同時(shí)簡要地介紹下其背后自定義注解的原理泉哈。

Lombok的簡介

Lombok是一款Java開發(fā)插件,使得Java開發(fā)者可以通過其定義的一些注解來消除業(yè)務(wù)工程中冗長和繁瑣的代碼破讨,尤其對(duì)于簡單的Java模型對(duì)象(POJO)。在開發(fā)環(huán)境中使用Lombok插件后奕纫,Java開發(fā)人員可以節(jié)省出重復(fù)構(gòu)建提陶,諸如hashCode和equals這樣的方法以及各種業(yè)務(wù)對(duì)象模型的accessor和ToString等方法的大量時(shí)間。對(duì)于這些方法匹层,它能夠在編譯源代碼期間自動(dòng)幫我們生成這些方法隙笆,并沒有如反射那樣降低程序的性能。

在Intellij中安裝Lombok的插件

想要體驗(yàn)一把Lombok的話升筏,得先在自己的開發(fā)環(huán)境中安裝上對(duì)應(yīng)的插件撑柔。通過IntelliJ的插件中心尋找Lombok并安裝。

另外需要注意的是您访,在使用lombok注解的時(shí)候記得要導(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ā)神器了檀训。下文將會(huì)給大家介紹Lombok中一些注解的使用方法,讓大家對(duì)如何用這些注解有一個(gè)大致的了解享言。

Lombok注解使用方法

Lombok常用注解介紹

下面先來看下Lombok中主要幾個(gè)常用注解介紹:

Val 可以將變量申明是final類型
@NonNull 能夠?yàn)榉椒ɑ驑?gòu)造函數(shù)的參數(shù)提供非空檢查
@Cleanup 能夠自動(dòng)釋放資源
@Getter/@Setter 可以針對(duì)類的屬性字段自動(dòng)生成Get/Set方法
@ToString 使用該注解的類生成一個(gè)toString方法
@EqualsAndHashCode 使用該注解的類自動(dòng)生成equals和hashCode方法
@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor,這幾個(gè)注解分別為類自動(dòng)生成了無參構(gòu)造器峻凫、指定參數(shù)的構(gòu)造器和包含所有參數(shù)的構(gòu)造器。
@Data注解作用比較全览露,其包含注解的集合@ToString荧琼,@EqualsAndHashCode,所有字段的@Getter和所有非final字段的@Setter, @RequiredArgsConstructor等
@Builder注解提供了一種比較推崇的構(gòu)建值對(duì)象的方式
@Synchronized注解類似Java中的Synchronized 關(guān)鍵字差牛,但是可以隱藏同步鎖

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) {
    //方法內(nèi)的代碼
}
//=>上面代碼相當(dāng)于如下:
public void notNullExample(String string) {
    if (string != null) {
        //方法內(nèi)的代碼相當(dāng)于如下:
    } else {
        throw new NullPointerException("null");
    }
}

(3)@Cleanup注解能夠自動(dòng)釋放資源多糠。

public   void jedisExample(String[] args) {
    try {
        @Cleanup Jedis jedis =   redisService.getJedis();
    } catch (Exception ex) {
        logger.error(“Jedis異常:”,ex)
    }
    //=>上面代碼相當(dāng)于如下:
    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注解可以針對(duì)類的屬性字段自動(dòng)生成Get/Set方法累舷。

public class OrderCreateDemoReq{
    @Getter
    @Setter
    private String customerId;
    @Setter
    @Getter
    private String poolId;
    //其他代碼……
}
//上面請(qǐng)求Req類的代碼相當(dāng)于如下:
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注解,為使用該注解的類生成一個(gè)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注解析孽,為使用該注解的類自動(dòng)生成equals和hashCode方法。

@EqualsAndHashCode(exclude = {"id"}, callSuper =true)
public class LombokDemo extends Demo{
    private int id;
    private String name;
    private String gender;
}
//上面代碼相當(dāng)于如下:
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,這幾個(gè)注解分別為類自動(dòng)生成了無參構(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)于如下:
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。其示例代碼可以參考上面幾個(gè)注解的組合贴谎。

(9)@Builder注解提供了一種比較推崇的構(gòu)建值對(duì)象的方式汞扎。

@Builder 
public class BuilderExample { 
  private String name; 
  private int age; 
  @Singular private Set<String> occupations; 
}
//上面代碼相當(dāng)于如下:
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 關(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ā)利器中各種定義注解的使用方法澈魄,但作為一個(gè)Java開發(fā)者來說光了解插件或者技術(shù)框架的用法只是做到了“知其然而不知其所以然”,如果真正掌握其背后的技術(shù)原理仲翎,看明白源碼設(shè)計(jì)理念才能真正做到“知其然知其所以然”痹扇。好了,話不多說下面進(jìn)入本章節(jié)的正題溯香,看下Lombok背后注解的深入原理鲫构。

可能熟悉Java自定義注解的同學(xué)已經(jīng)猜到,Lombok這款插件正是依靠可插件化的Java自定義注解處理API(JSR 269: Pluggable Annotation Processing API)來實(shí)現(xiàn)在Javac編譯階段利用“Annotation Processor”對(duì)自定義的注解進(jìn)行預(yù)處理后生成真正在JVM上面執(zhí)行的“Class文件”玫坛。有興趣的同學(xué)反編譯帶有Lombok注解的類文件也就一目了然了结笨。其大致執(zhí)行原理圖如下:

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

從上面的Lombok執(zhí)行的流程圖中可以看出湿镀,在Javac 解析成AST抽象語法樹之后, Lombok 根據(jù)自己編寫的注解處理器禀梳,動(dòng)態(tài)地修改 AST,增加新的節(jié)點(diǎn)(即Lombok自定義注解所需要生成的代碼)肠骆,最終通過分析生成JVM可執(zhí)行的字節(jié)碼Class文件算途。使用Annotation Processing自定義注解是在編譯階段進(jìn)行修改,而JDK的反射技術(shù)是在運(yùn)行時(shí)動(dòng)態(tài)修改蚀腿,兩者相比嘴瓤,反射雖然更加靈活一些但是帶來的性能損耗更加大。

需要更加深入理解Lombok插件的細(xì)節(jié)莉钙,自己查閱其源代碼是必比可少的廓脆。對(duì)開源框架代碼比較有執(zhí)著追求的童鞋可以將Lombok的源代碼工程從github上download到本地進(jìn)行閱讀和自己調(diào)試。下圖為Lombok工程源代碼的截圖:

從熟悉JSR 269: Pluggable Annotation Processing API的同學(xué)可以從工程類結(jié)構(gòu)圖中發(fā)現(xiàn)AnnotationProcessor這個(gè)類是Lombok自定義注解處理的入口磁玉。該類有兩個(gè)比較重要的方法一個(gè)是init方法停忿,另外一個(gè)是process方法。在init方法中蚊伞,先用來做參數(shù)的初始化席赂,將AnnotationProcessor類中定義的內(nèi)部類(JavacDescriptor吮铭、EcjDescriptor)先注冊(cè)到ProcessorDescriptor類型定義的列表中。其中颅停,內(nèi)部靜態(tài)類—JavacDescriptor在其加載的時(shí)候就將 lombok.javac.apt.LombokProcessor這個(gè)類進(jìn)行對(duì)象實(shí)例化并注冊(cè)谓晌。

LombokProcessor處理器中,其中的process方法會(huì)根據(jù)優(yōu)先級(jí)來分別運(yùn)行相應(yīng)的handler處理類癞揉。Lombok中的多個(gè)自定義注解都分別有對(duì)應(yīng)的handler處理類纸肉,如下圖所示:

可以看出,在Lombok中對(duì)于其自定義注解進(jìn)行實(shí)際的替換喊熟、修改和處理的正是這些handler類柏肪。對(duì)于其實(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í)行處理流程。限于篇幅宿礁,未能對(duì)“自定義Java注解處理器”的具體實(shí)踐和原理進(jìn)行闡述案铺,后面將另起專題篇幅進(jìn)行介紹。限于筆者的才疏學(xué)淺梆靖,對(duì)本文內(nèi)容可能還有理解不到位的地方控汉,如有闡述不合理之處還望留言一起探討。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末返吻,一起剝皮案震驚了整個(gè)濱河市姑子,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌测僵,老刑警劉巖街佑,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捍靠,居然都是意外死亡沐旨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門榨婆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磁携,“玉大人,你說我怎么就攤上這事良风∫昶” “怎么了闷供?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鳞上。 經(jīng)常有香客問我这吻,道長,這世上最難降的妖魔是什么篙议? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任唾糯,我火速辦了婚禮,結(jié)果婚禮上鬼贱,老公的妹妹穿的比我還像新娘移怯。我一直安慰自己,他們只是感情好这难,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布舟误。 她就那樣靜靜地躺著,像睡著了一般姻乓。 火紅的嫁衣襯著肌膚如雪嵌溢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天蹋岩,我揣著相機(jī)與錄音赖草,去河邊找鬼。 笑死剪个,一個(gè)胖子當(dāng)著我的面吹牛秧骑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扣囊,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乎折,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了侵歇?” 一聲冷哼從身側(cè)響起骂澄,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惕虑,沒想到半個(gè)月后酗洒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枷遂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年樱衷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酒唉。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矩桂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侄榴,我是刑警寧澤雹锣,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站癞蚕,受9級(jí)特大地震影響蕊爵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桦山,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一攒射、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恒水,春花似錦会放、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至御雕,卻和暖如春矢沿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酸纲。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工捣鲸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人福青。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像脓诡,于是被迫代替她去往敵國和親无午。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355