Lombok經常用,但是你知道它的原理是什麼嗎?

不學無數的程式設計師發表於2020-02-26

相信大家在專案中都使用過Lombok,因為能夠簡化我們許多的程式碼,但是該有的功能一點也不少。那麼lombok到底是個什麼呢,lombok是一個可以通過簡單的註解的形式來幫助我們簡化消除一些必須有但顯得很臃腫的 Java 程式碼的工具,簡單來說,比如我們新建了一個類,然後在其中寫了幾個欄位,然後通常情況下我們需要手動去建立getter和setter方法啊,建構函式啊之類的,lombok的作用就是為了省去我們手動建立這些程式碼的麻煩,它能夠在我們編譯原始碼的時候自動幫我們生成這些方法。

那麼Lombok到底是如何做到這些的呢?其實底層就是用到了編譯時註解的功能。

Lombok如何使用

Lombok是一個開源專案,程式碼是在lombok中,如果是gradle專案的話直接在專案中引用如下即可。

1compile ("org.projectlombok:lombok:1.16.6")
複製程式碼

功能

那麼Lombok是做什麼呢?其實很簡單,一個最簡單的例子就是能夠通過新增註解自動生成一些方法,使我們程式碼更加簡潔易懂。例如下面一個類。

 1@Data
2public class TestLombok {
3    private String name;
4    private Integer age;
5
6    public static void main(String[] args) {
7        TestLombok testLombok = new TestLombok();
8        testLombok.setAge(12);
9        testLombok.setName("zs");
10    }
11}
複製程式碼

我們使用Lombok提供的Data註解,在沒有寫get、set方法的時候也能夠使用其get、set方法。我們看它編譯過後的class檔案,可以看到它給我們自動生成了get、set方法。

 1public class TestLombok {
2    private String name;
3    private Integer age;
4
5    public static void main(String[] args) {
6        TestLombok testLombok = new TestLombok();
7        testLombok.setAge(12);
8        testLombok.setName("zs");
9    }
10
11    public TestLombok() {
12    }
13
14    public String getName() {
15        return this.name;
16    }
17
18    public Integer getAge() {
19        return this.age;
20    }
21
22    public void setName(String name) {
23        this.name = name;
24    }
25
26    public void setAge(Integer age) {
27        this.age = age;
28    }
29
30}
複製程式碼

當然Lombok的功能不止如此,還有很多其他的註解幫助我們簡便開發,網上有許多的關於Lombok的使用方法,這裡就不再囉嗦了。正常情況下我們在專案中自定義註解,或者使用Spring框架中@Controller、@Service等等這類註解都是執行時註解,執行時註解大部分都是通過反射來實現的。而Lombok是使用編譯時註解實現的。那麼編譯時註解是什麼呢?

編譯時註解

註解(也被成為後設資料)為我們在程式碼中新增資訊提供了一種形式化的方法,使我們可以在稍後某個時刻非常方便地使用這些資料。 ——————摘自《Thinking in Java》

Java中的註解分為執行時註解編譯時註解,執行時註解就是我們經常使用的在程式執行時通過反射得到我們註解的資訊,然後再做一些操作。而編譯時註解是什麼呢?就是在程式在編譯期間通過註解處理器進行處理。

  • 編譯期:Java語言的編譯期是一段不確定的操作過程,因為它可能是將.java`檔案轉化成`.class檔案的過程;也可能是指將位元組碼轉變成機器碼的過程;還可能是直接將*.java編譯成本地機器程式碼的過程
  • 執行期:從JVM載入位元組碼檔案到記憶體中,到最後使用完畢以後解除安裝的過程都屬於執行期的範疇。

註解處理工具apt

註解處理工具apt(Annotation Processing Tool),這是Sun為了幫助註解的處理過程而提供的工具,apt被設計為操作Java原始檔,而不是編譯後的類。

它是javac的一個工具,中文意思為編譯時註解處理器。APT可以用來在編譯時掃描和處理註解。通過APT可以獲取到註解和被註解物件的相關資訊,在拿到這些資訊後我們可以根據需求來自動的生成一些程式碼,省去了手動編寫。注意,獲取註解及生成程式碼都是在程式碼編譯時候完成的,相比反射在執行時處理註解大大提高了程式效能。APT的核心是AbstractProcessor類。

正常情況下使用APT工具只是能夠生成一些檔案(不僅僅是我們想象的class檔案,還包括xml檔案等等之類的),並不能修改原有的檔案資訊。

但是此時估計會有疑問,那麼Lombok不就是在我們原有的檔案中新增了一些資訊嗎?我在後面會有詳細的解釋,這裡簡單介紹一下,其實Lombok是修改了Java中的抽象語法樹AST才做到了修改其原有類的資訊。

接下來我們演示一下如何用APT工具生成一個class檔案,然後我們再說Lombok是如何修改已存在的類中的屬性的。

定義註解

首先當然我們需要定義自己的註解了

1@Retention(RetentionPolicy.SOURCE) // 註解只在原始碼中保留
2@Target(ElementType.TYPE) // 用於修飾類
3public @interface GeneratePrint {
4
5    String value();
6}
複製程式碼

Retention註解上面有一個屬性value,它是RetentionPolicy型別的列舉類,RetentionPolicy列舉類中有三個值。

1public enum RetentionPolicy {
2
3    SOURCE,
4
5    CLASS,
6
7    RUNTIME
8}
複製程式碼
  • SOURCE修飾的註解:修飾的註解,表示註解的資訊會被編譯器拋棄,不會留在class檔案中,註解的資訊只會留在原始檔中
  • CLASS修飾的註解:表示註解的資訊被保留在class檔案(位元組碼檔案)中當程式編譯時,但不會被虛擬機器讀取在執行的時候
  • RUNTIME修飾的註解:表示註解的資訊被保留在class檔案(位元組碼檔案)中當程式編譯時,會被虛擬機器保留在執行時。所以它能夠通過反射呼叫,所以正常執行時註解都是使用的這個引數

Target註解上面也有個屬性value,它是ElementType型別的列舉。是用來修飾此註解作用在哪的。

 1public enum ElementType {
2    TYPE,
3
4    FIELD,
5
6    METHOD,
7
8    PARAMETER,
9
10    CONSTRUCTOR,
11
12    LOCAL_VARIABLE,
13
14    ANNOTATION_TYPE,
15
16    PACKAGE,
17
18    TYPE_PARAMETER,
19
20    TYPE_USE
21}
複製程式碼

定義註解處理器

我們要定義註解處理器的話,那麼就需要繼承AbstractProcessor類。繼承完以後基本的框架型別如下

 1@SupportedSourceVersion(SourceVersion.RELEASE_8)
2@SupportedAnnotationTypes("aboutjava.annotion.MyGetter")
3public class MyGetterProcessor extends AbstractProcessor {
4    @Override
5    public synchronized void init(ProcessingEnvironment processingEnv) {
6    super.init(processingEnv);
7    }
8
9    @Override
10    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
11        return true;
12    }
13}
複製程式碼

我們可以看到在子類中上面有兩個註解,註解描述如下

  • @SupportedSourceVersion:表示所支援的Java版本
  • @SupportedAnnotationTypes:表示該處理器要處理的註解

繼承了父類的兩個方法,方法描述如下

  • init方法:主要是獲得編譯時期的一些環境資訊
  • process方法:在編譯時,編譯器執行的方法。也就是我們寫具體邏輯的地方

我們是演示一下如何通過繼承AbstractProcessor類來實現在編譯時生成類,所以我們在process方法中書寫我們生成類的程式碼。如下所示。

 1@Override
2public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
3    StringBuilder builder = new StringBuilder()
4            .append("package aboutjava.annotion;\n\n")
5            .append("public class GeneratedClass {\n\n"// open class
6            .append("\tpublic String getMessage() {\n"// open method
7            .append("\t\treturn \"");
8    // for each javax.lang.model.element.Element annotated with the CustomAnnotation
9    for (Element element : roundEnv.getElementsAnnotatedWith(MyGetter.class)) {
10        String objectType = element.getSimpleName().toString();
11        // this is appending to the return statement
12        builder.append(objectType).append(" says hello!\\n");
13    }
14    builder.append("\";\n"// end return
15            .append("\t}\n"// close method
16            .append("}\n"); // close class
17    try { // write the file
18        JavaFileObject source = processingEnv.getFiler().createSourceFile("aboutjava.annotion.GeneratedClass");
19        Writer writer = source.openWriter();
20        writer.write(builder.toString());
21        writer.flush();
22        writer.close();
23    } catch (IOException e) {
24        // Note: calling e.printStackTrace() will print IO errors
25        // that occur from the file already existing after its first run, this is normal
26    }
27    return true;
28}
複製程式碼

定義使用註解的類(測試類)

上面的兩個類就是基本的工具類了,一個是定義了註解,一個是定義了註解處理器,接下來我們來定義一個測試類(TestAno.java)。我們在類上面加上我們自定的註解類。

1@MyGetter
2public class TestAno {
3
4    public static void main(String[] args) {
5        System.out.printf("1");
6    }
7}
複製程式碼

這樣我們在編譯期就能生成檔案了,接下來演示一下在編譯時生成檔案,此時不要著急直接進行javac編譯,MyGetter類是註解類沒錯,而MyGetterProcessor是註解類的處理器,那麼我們在編譯TestAnoJava檔案的時候就會觸發處理器。因此這兩個類是無法一起編譯的。

先給大家看一下我的目錄結構

1aboutjava
2    -- annotion
3        -- MyGetter.java
4        -- MyGetterProcessor.java
5        -- TestAno.java
複製程式碼

所以我們先將註解類和註解處理器類進行編譯

1javac aboutjava/annotion/MyGett*
複製程式碼

接下來進行編譯我們的測試類,此時在編譯時需要加上processor引數,用來指定相關的註解處理類。

1javac -processor aboutjava.annotion.MyGetterProcessor aboutjava/annotion/TestAno.java
複製程式碼

大家可以看到動態圖中,自動生成了Java檔案。

程式碼地址

總結

本篇文章還會有第二篇進行講解Lombok的原理,如何修改原有類的內容。本篇作為前置知識,簡單的介紹了註解處理器是什麼,如何利用註解處理器做一些我們在編譯期才能夠做的事情。希望大家能夠自己在本機上試驗一下,如果本篇有任何問題歡迎指出。

有感興趣的可以關注一下我新建的公眾號,搜尋[程式猿的百寶袋]。或者直接掃下面的碼也行。

參考

相關文章