Lombok簡介
Lombok(https://projectlombok.org/) 提供了以注解的形式為java對象增加屬性和方法,這使得原來冗長的java源文件變的簡潔(不需要再使用ide去生成getter和setter方法,不過ide需要插件支持才能識別lombok自動添加的getter/setter方法,因為真正的代碼其實是在編譯階段添加到子節碼文件中的),一些固定模板的代碼也可以自動添加,例如生成Logger。詳細的原理有很多分析貼,此處不再贅述,有需要可以參見附錄中的鏈接:http://notatube.blogspot.com/2010/11/project-lombok-trick-explained.html
Lombok中自帶的注解幾乎涵蓋了所有冗余的,但總是會有一些需要我們自己定制的需求。
研究內容
最近遇到一個情景,一些JAVA對象都需要一個映射到數據庫自增ID的字段,為了避免代碼冗余,讓他們繼承自同一個包含同一個id字段的父類似乎就可以解決問題,不同的主鍵還有可能是不同的數據類型,較常見的是Long,Integer和String(uuid),所以這個父類需要寫成一個模板類,並且要求所有包含主鍵的類都要繼承這個泛型父類,其實我們只是想要的只是這個類增加一個id字段,並且生成對應的getter和setter 方法就好了,與其搞成繼承關系,倒不如讓效法lombok,使用annotation來生成這個字段和方法。
本文探討的內容包括:
-
新增一個PrimaryKey注解,接受keyName和keyType兩個參數,凡是有PrimaryKey注解的類,都會自動生成一個類型是${keyType}、字段名為${keyName}的字段,並為這個字段生成getter和setter方法。
-
讓intellij的lombok插件能夠識別新的注解,可以自動提示主鍵字段和它的getter/setter方法,而不報編譯錯誤
自定義注解
不僅要新建自定義注解,還要新建對應的Handler,以便編譯階段正確處理包含自定義注解的源文件。
開發環境設置
首先checkout最新的lombok代碼,https://github.com/rzwitserloot/lombok.git
lombok項目目前只有基於Eclipse的debug環境配置腳本,如果是Intellij,需要自己配置一下lombok的環境,稍后會介紹。
工程中有基於ant的自動構建腳本,先運行"compile" 任務,這時會自動觸發"ensureBuildDeps"這個任務,它會下載一些依賴的Jar包。Tips:中間會去下一個對應jdk版本的rt.jar文件,由於這個文件比較大,國內下載速度非常慢,建議自行翻|牆下載,放到lib目錄對應java版本的文件夾下面即可。
編譯成功之后,就可以跑一下"test"任務了,它會先執行"dist",在dist目錄下面生成lombok的jar文件,然后執行自動測試。
新建自定義注解
注解類代碼如下:
package lombok; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 默認以Long作為主鍵類型 * Created by hoyt on 15/9/22. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface PrimaryKey { Class keyType() default Long.class; String keyName() default "id"; }
新建HandlePrimaryKey
handler需要繼承自JavacAnnotationHandler,並實現其handle方法,簡述一下handle方法里的操作
-
判斷目標注解(這里就是PrimaryKey)是否滿足正確性驗證
-
驗證通過時,為被注解的類動態添加字段,字段的名稱和類型都是PrimaryKey注解中的值
-
為新添加的字段創建getter和setter方法
handle方法的聲明如下:
public void handle(AnnotationValues<PrimaryKey> annotation, JCTree.JCAnnotation ast, JavacNode annotationNode){ }
其中,annotation是lombok自定義的類型,包含了自定義的注解信息,通過如下代碼,就可以獲得注解中設置的具體值
String keyName = annotation.getInstance().keyName(); Class pk = annotation.getInstance().keyType();
在新建字段、創建getter/setter方法時,都可以直接復用lombok已有的方法和類,非常方便,貼出部分關鍵代碼。
JCTree.JCExpression keyType = chainDotsString(typeNode, type); JCTree.JCVariableDecl field = maker.VarDef( maker.Modifiers(Flags.PRIVATE), typeNode.toName(keyName), keyType, null); JCTree.JCVariableDecl fieldDecl = recursiveSetGeneratedBy(field, source, typeNode.getContext()); JavacNode fieldNode = injectFieldAndMarkGenerated(typeNode, fieldDecl); new HandleGetter().generateGetterForField(fieldNode, source.pos(), AccessLevel.PUBLIC, false); new HandleSetter().generateSetterForField(fieldNode, typeNode, AccessLevel.PUBLIC); return true;
*如果要支持Eclipse自帶的編譯器,需要另外寫一個handler
測試Handle
幸好lombok已經包含了測試/調試環境配置(只針對eclipse,Intellij需要自行配置)。直接運行"setupJavaOracle8TestEnvironment",就會自動配置好基於junit的測試/調試環境。這時打開"Run Configurations",就會發現Junit的測試里多了一種"RunLombokTest"...如下圖所示:
實際上沒有必要跑全部的junit測試,只要跑TestWithDelombok即可
lombok的單元測試是這樣的:在test/transform/resource目錄里,有三個文件夾,before,after-delombok和after-ejc,before中是所有測試用的java文件,而after-delombok目錄中包含的則是lombok.jar參與編譯后生成的java源文件,單元測試時,會將after-delombok中的文件作為expected value去同生成的代碼做比較,如果一致就認為通過測試。因此單元測試時,在before目錄中創建一個class,並為之加上自定義注解,同時在after-lombok中新建一個同名類,把預期的字段和方法寫進去即可。after-ejc用於測試eclipse的java編譯器結果。注:lombok在處理字段和方法時,會加上額外的標注注解。
至於Intellij,做調試相對就麻煩一點,需要打開一個intellij的新實例,新建一個項目,在這個項目中引入修改后的lombok,然后加上自定義的注解,這時會觸發原來那個intellij中lombok的代碼運行,此時斷點就會被觸發。
Intellij-lombok插件
自定義的注解已經完成,在javac時指定javaagent為新打包出來的lombok.jar即可,不過蛋疼的是,ide此時尚不能識別自定義的注解,為了能夠讓集成開發環境與新添加的自定義注解合作無間,還需要對Intellij的lombok插件做一番改造。
今天相關的代碼見:https://git.oschina.net/hoyt/lombok.git,明天繼續補完Intellij插件的工作。