註解處理器初探
平時做項目中有個非常好用的一個插件,叫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 {
}
1.@Target:表示的是這個註解在什麼上面使用,這裏ElementType.TYPE是指在類上使用該註解
2.@Retention:表示的是保留到什麼階段,這裏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;
}
}
1.@SupportedAnnotationTypes:表示這個processor類要對什麼註解生效
2.@SupportedSourceVersion:表示支持的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/javas...
2.https://jcp.org/aboutJava/com...
3.https://github.com/square/jav...
4.https://www.cnblogs.com/throw...
5.http://notatube.blogspot.com/...
6.https://www.baeldung.com/java...
7.http://hannesdorfmann.com/ann...