面試官:你天天用 Lombok,說說它什么原理?我竟然答不上來…
相信大家在項目中都使用過Lombok,因為能夠簡化我們許多的代碼,但是該有的功能一點也不少。
那么lombok到底是個什么呢,lombok是一個可以通過簡單的注解的形式來幫助我們簡化消除一些必須有但顯得很臃腫的 Java 代碼的工具,簡單來說,比如我們新建了一個類,然后在其中寫了幾個字段,然后通常情況下我們需要手動去建立getter和setter方法啊,構造函數啊之類的,lombok的作用就是為了省去我們手動創(chuàng)建這些代碼的麻煩,它能夠在我們編譯源碼的時候自動幫我們生成這些方法。
那么Lombok到底是如何做到這些的呢?其實底層就是用到了編譯時注解的功能。
Lombok如何使用
Lombok是一個開源項目,代碼是在lombok中,如果是gradle項目的話直接在項目中引用如下即可。
compile?("org.projectlombok:lombok:1.16.6")
功能
那么Lombok是做什么呢?其實很簡單,一個最簡單的例子就是能夠通過添加注解自動生成一些方法,使我們代碼更加簡潔易懂。例如下面一個類。
@Data
public?class?TestLombok?{
????private?String?name;
????private?Integer?age;
????public?static?void?main(String[]?args)?{
????????TestLombok?testLombok?=?new?TestLombok();
????????testLombok.setAge(12);
????????testLombok.setName("zs");
????}
?}
我們使用Lombok提供的Data注解,在沒有寫get、set方法的時候也能夠使用其get、set方法。我們看它編譯過后的class文件,可以看到它給我們自動生成了get、set方法。
public?class?TestLombok?{
????private?String?name;
????private?Integer?age;
????public?static?void?main(String[]?args)?{
????????TestLombok?testLombok?=?new?TestLombok();
????????testLombok.setAge(12);
????????testLombok.setName("zs");
????}
????public?TestLombok()?{
????}
????public?String?getName()?{
????????return?this.name;
????}
????public?Integer?getAge()?{
????????return?this.age;
????}
????public?void?setName(String?name)?{
????????this.name?=?name;
????}
????public?void?setAge(Integer?age)?{
????????this.age?=?age;
????}
}
當然Lombok的功能不止如此,還有很多其他的注解幫助我們簡便開發(fā),網上有許多的關于Lombok的使用方法,這里就不再啰嗦了。正常情況下我們在項目中自定義注解,或者使用Spring框架中@Controller、@Service等等這類注解都是運行時注解,運行時注解大部分都是通過反射來實現的。而Lombok是使用編譯時注解實現的。那么編譯時注解是什么呢?
編譯時注解
注解(也被成為元數據)為我們在代碼中添加信息提供了一種形式化的方法,使我們可以在稍后某個時刻非常方便地使用這些數據。——摘自《Thinking in Java》
Java中的注解分為運行時注解 和編譯時注解 ,運行時注解就是我們經常使用的在程序運行時通過反射得到我們注解的信息,然后再做一些操作。而編譯時注解是什么呢?就是在程序在編譯期間通過注解處理器進行處理。
編譯期:Java語言的編譯期是一段不確定的操作過程,因為它可能是將 *.java文件轉化成*.class文件的過程;也可能是指將字節(jié)碼轉變成機器碼的過程;還可能是直接將*.java編譯成本地機器代碼的過程運行期:從JVM加載字節(jié)碼文件到內存中,到最后使用完畢以后卸載的過程都屬于運行期的范疇。
注解處理工具apt
注解處理工具apt(Annotation Processing Tool),這是Sun為了幫助注解的處理過程而提供的工具,apt被設計為操作Java源文件,而不是編譯后的類。
它是javac的一個工具,中文意思為編譯時注解處理器。APT可以用來在編譯時掃描和處理注解。通過APT可以獲取到注解和被注解對象的相關信息,在拿到這些信息后我們可以根據需求來自動的生成一些代碼,省去了手動編寫。注意,獲取注解及生成代碼都是在代碼編譯 時候完成的,相比反射在運行時處理注解大大提高了程序性能。APT的核心是AbstractProcessor類。
正常情況下使用APT工具只是能夠生成一些文件(不僅僅是我們想象的class文件,還包括xml文件等等之類的 ),并不能修改原有的文件信息。
但是此時估計會有疑問,那么Lombok不就是在我們原有的文件中新增了一些信息嗎?我在后面會有詳細的解釋,這里簡單介紹一下,其實Lombok是修改了Java中的抽象語法樹AST 才做到了修改其原有類的信息。
接下來我們演示一下如何用APT工具生成一個class文件,然后我們再說Lombok是如何修改已存在的類中的屬性的。
定義注解
首先當然我們需要定義自己的注解了
?@Retention(RetentionPolicy.SOURCE)
?@Target(ElementType.TYPE)
?public?@interface?GeneratePrint?{
????String?value();
?}
Retention注解上面有一個屬性value,它是RetentionPolicy類型的枚舉類,RetentionPolicy枚舉類中有三個值。
?public?enum?RetentionPolicy?{
????SOURCE,
????CLASS,
????RUNTIME
?}
SOURCE修飾的注解:修飾的注解,表示注解的信息會被編譯器拋棄,不會留在class文件中,注解的信息只會留在源文件中CLASS修飾的注解:表示注解的信息被保留在class文件(字節(jié)碼文件)中當程序編譯時,但不會被虛擬機讀取在運行的時候RUNTIME修飾的注解:表示注解的信息被保留在class文件(字節(jié)碼文件)中當程序編譯時,會被虛擬機保留在運行時。所以它能夠通過反射調用,所以正常運行時注解都是使用的這個參數
Target注解上面也有個屬性value,它是ElementType類型的枚舉。是用來修飾此注解作用在哪的。
public?enum?ElementType?{
????TYPE,
????FIELD,
????METHOD,
????PARAMETER,
????CONSTRUCTOR,
????LOCAL_VARIABLE,
????ANNOTATION_TYPE,
????PACKAGE,
????TYPE_PARAMETER,
????TYPE_USE
?}
定義注解處理器
我們要定義注解處理器的話,那么就需要繼承AbstractProcessor類。繼承完以后基本的框架類型如下
?@SupportedSourceVersion(SourceVersion.RELEASE_8)
?@SupportedAnnotationTypes("aboutjava.annotion.MyGetter")
?public?class?MyGetterProcessor?extends?AbstractProcessor?{
????@Override
????public?synchronized?void?init(ProcessingEnvironment?processingEnv)?{
????super.init(processingEnv);
????}
????@Override
????public?boolean?process(Set?extends?TypeElement>?annotations,?RoundEnvironment?roundEnv)?{
????????return?true;
????}
}
我們可以看到在子類中上面有兩個注解,注解描述如下
@SupportedSourceVersion:表示所支持的Java版本@SupportedAnnotationTypes:表示該處理器要處理的注解
繼承了父類的兩個方法,方法描述如下
init方法:主要是獲得編譯時期的一些環(huán)境信息 process方法:在編譯時,編譯器執(zhí)行的方法。也就是我們寫具體邏輯的地方
我們是演示一下如何通過繼承AbstractProcessor類來實現在編譯時生成類,所以我們在process方法中書寫我們生成類的代碼。如下所示。
@Override
public?boolean?process(Set?extends?TypeElement>?annotations,?RoundEnvironment?roundEnv)?{
????StringBuilder?builder?=?new?StringBuilder()
????????????.append("package?aboutjava.annotion;\n\n")
????????????.append("public?class?GeneratedClass?{\n\n")
????????????.append("\tpublic?String?getMessage()?{\n")
????????????.append("\t\treturn?\"");
????for?(Element?element?:?roundEnv.getElementsAnnotatedWith(MyGetter.class))?{
????????String?objectType?=?element.getSimpleName().toString();
????????builder.append(objectType).append("?says?hello!\\n");
????}
????builder.append("\";\n")
????????????.append("\t}\n")
????????????.append("}\n");
????try?{
????????JavaFileObject?source?=?processingEnv.getFiler().createSourceFile("aboutjava.annotion.GeneratedClass");
????????Writer?writer?=?source.openWriter();
????????writer.write(builder.toString());
????????writer.flush();
????????writer.close();
????}?catch?(IOException?e)?{
????}
????return?true;
}
定義使用注解的類(測試類)
上面的兩個類就是基本的工具類了,一個是定義了注解,一個是定義了注解處理器,接下來我們來定義一個測試類(TestAno.java)。我們在類上面加上我們自定的注解類。
?@MyGetter
?public?class?TestAno?{
????public?static?void?main(String[]?args)?{
????????System.out.printf("1");
????}
?}
這樣我們在編譯期就能生成文件了,接下來演示一下在編譯時生成文件,此時不要著急直接進行javac編譯,MyGetter類是注解類沒錯,而MyGetterProcessor是注解類的處理器,那么我們在編譯TestAnoJava文件的時候就會觸發(fā)處理器。因此這兩個類是無法一起編譯的。
先給大家看一下我的目錄結構
aboutjava2
--?annotion3
--?MyGetter.java4
--?MyGetterProcessor.java5
--?TestAno.java
所以我們先將注解類和注解處理器類進行編譯
javac?aboutjava/annotion/MyGett*
接下來進行編譯我們的測試類,此時在編譯時需要加上processor參數,用來指定相關的注解處理類。
javac?-processor?aboutjava.annotion.MyGetterProcessor?aboutjava/annotion/TestAno.java
大家可以看到動態(tài)圖中,自動生成了Java文件。

來源:juejin.im/post/5e54d38a6fb9a07cbf46b3ca
2022-05-27
2022-05-25
2022-05-16
2022-05-14
2022-05-13
如果你覺得這篇文章不錯,那么,下篇通常會更好。備注“公眾號”添加微信好友(微信號:zhuan2quan)。
