注解處理器初探
平時做項目中有個非常好用的一個插件,叫lombok.它提供了一些簡單的注解,可以用來生成javabean和一些getter/setter方法,提高了開發的效率節省了開發時間.
今天我們就來看看lombok使用的什麼方式來實作這種操作的.其實lombok使用的是annotation processor,這個是jdk1.5中增加的新功能.像@Getter隻是一個注解,它真正的處理部分
是在注解處理器裡面實作的.官方參考連結.
背景介紹
注解處理器其實全稱叫Pluggable Annotation Processing API,插入式注解處理器,它是對JSR269提案的實作,具體可以看連結裡面的内容,JSR269連結.
它是怎麼工作的呢?可以參考下圖:
1.parse and enter:解析和輸入,java編譯器這個階段會把源代碼解析生成AST(抽象文法分析樹)
2.annotation processing:注解處理器階段,此時将調用注解處理器,這時候可以校驗代碼,生成新檔案等等(處理完可以循環到第一步)
3.analyse and generate:分析和生成,此時前兩步完成後,生成位元組碼(這個階段進行了解糖,比如類型擦除)
這些其實隻是為了給大家留有一個粗淺的印象,它是怎麼執行的.
實踐
看了上面的資料,大腦中應該有了一個大概的印象,現在我們實際操作一下寫一個簡單的例子,實踐一下.
要使用注解處理器需要兩個步驟:
1.自定義一個注解
2.繼承AbstractProcessor并且實作process方法
我們接下來寫一個很簡單的例子,就是在一個類上加上@InterfaceAnnotation,編譯的時候去生成一個"I"+類名的接口類.
首先我這裡是定義了兩個moudle,一個用來寫注解和處理器,另一個用來調用注解.
第一步:自定義一個注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface InterfaceAnnotation {
}
[email protected]:表示的是這個注解在什麼上面使用,這裡ElementType.TYPE是指在類上使用該注解
[email protected]:表示的是保留到什麼階段,這裡RetentionPolicy.SOURCE是源代碼階段,編譯後的class上就沒有這個注解了
第二步:繼承AbstractProcessor并且實作process方法
@SupportedAnnotationTypes(value = {"com.example.processor.InterfaceAnnotation"})
@SupportedSourceVersion(value = SourceVersion.RELEASE_8)
public class InterfaceProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "進入到InterfaceProcessor中了~~~");
// 将帶有InterfaceProcessor的類給找出來
Set<? extends Element> clazz = roundEnv.getElementsAnnotatedWith(InterfaceAnnotation.class);
clazz.forEach(item -> {
// 生成一個 I + 類名的接口類
String className = item.getSimpleName().toString();
className = "I" + className.substring(0, 1) + className.substring(1);
TypeSpec typeSpec = TypeSpec.interfaceBuilder(className).addModifiers(Modifier.PUBLIC).build();
try {
// 生成java檔案
JavaFile.builder("com.example.processor", typeSpec).build().writeTo(new File("./src/main/java/"));
} catch (IOException e) {
e.printStackTrace();
}
});
return true;
}
}
[email protected]:表示這個processor類要對什麼注解生效
[email protected]:表示支援的java版本
3.annotations:被要求的注解,就是@SupportedAnnotationTypes對應的注解
4.roundEnv:存放着目前和上一輪processing的環境資訊
5.TypeSpec這個可能有點沒看懂是幹嘛的,它是javaPoet中的一個類,javaPoet是java用于生成java檔案的一款第三方插件很好用,是以這裡使用了這個類來生成java檔案,
實際上這裡用java自帶的PrintWriter等輸入輸出流也可以生成java檔案,生成檔案有很多方式.javaPoet的連結.javaPoet使用指南.
6.Messager是用來列印輸出資訊的,System.out.println其實也可以;
7.process如果傳回是true後續的注解處理器就不會再處理這個注解,如果是false,在下一輪processing中,其他注解處理器也會來處理改注解.
寫好之後,這裡需要指定processor,META-INF/services/javax.annotation.processing.Processor 寫好com.example.processor.InterfaceProcessor.如果你不知道這是啥,可以看下我另一篇部落格(實力推廣XD)什麼是SPI
我們在把注解處理器給編譯好,maven裡插件的設定:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- 不加這一句編譯會報找不到processor的異常-->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
此時的目錄結構是這樣:
.
├── HELP.md
├── pom.xml
├── processor.iml
└── src
└── main
├── java
│ └── com
│ └── example
│ └── processor
│ ├── InterfaceAnnotation.java
│ └── InterfaceProcessor.java
└── resources
└── META-INF
└── services
└── javax.annotation.processing.Processor
然後mvn clean install.
第三步:使用注解
在使用之前呢,注解處理器要是編譯好的.引入注解處理器的jar包.
測試類加上@InterfaceAnnotation
@InterfaceAnnotation
public class TestProcessor {
}
maven指定編譯時使用的注解處理器.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<annotationProcessors>
<annotationProcessor>
com.example.processor.InterfaceProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
此時目錄結構是
.
├── HELP.md
├── pom.xml
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── test
│ │ └── TestProcessor.java
│ └── resources
└── test.iml
然後mvn compile,生成了java檔案,此時目錄結構是:
.
├── HELP.md
├── pom.xml
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ ├── processor
│ │ │ └── ITestProcessor.java // 這裡就是生成的java檔案
│ │ └── test
│ │ └── TestProcessor.java
│ └── resources
├── target
│ ├── classes
│ │ └── com
│ │ └── example
│ │ └── test
│ │ └── TestProcessor.class
│ ├── generated-sources
│ │ └── annotations
│ └── maven-status
│ └── maven-compiler-plugin
│ └── compile
│ └── default-compile
│ ├── createdFiles.lst
│ └── inputFiles.lst
└── test.iml
看到了生成的java檔案就大功告成~
總結:
1.java注解處理器在很多地方都可以使用,實際應用比如lombok,安卓生成fragment等等,隻使用一個注解可以省去很多代碼,提高效率;
2.本文隻是列舉了一個很簡單的例子,很多注解處理器裡面的api都沒有使用到,讀者有興趣的可以自行研究,而且有涉及到抽象文法樹的api;
3.注解處理器可以用于生成新的類來完成某些功能,但是不能直接修改目前的類.
參考資料:
1.https://docs.oracle.com/javase/8/docs/api/javax/annotation/processing/Processor.html
2.https://jcp.org/aboutJava/communityprocess/final/jsr269/index.html
3.https://github.com/square/javapoet
4.https://www.cnblogs.com/throwable/p/9139908.html
5.http://notatube.blogspot.com/2010/11/project-lombok-trick-explained.html(介紹處理過程)
6.https://www.baeldung.com/java-annotation-processing-builder
7.http://hannesdorfmann.com/annotation-processing/annotationprocessing101