註解處理器是幹嘛的

註解處理器初探

    平時做項目中有個非常好用的一個插件,叫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...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章