1. 程式人生 > >自定義註解之編譯時註解(RetentionPolicy.CLASS)(二)——JavaPoet

自定義註解之編譯時註解(RetentionPolicy.CLASS)(二)——JavaPoet

在使用編譯時註解時,需要在編譯期間對註解進行處理,在這裡我們沒辦法影響程式的執行邏輯,但我們可以進行一些需處理,比如生成一些功能性程式碼來輔助程式的開發,最常見的是生成.java 原始檔,並在程式中可以呼叫到生成的檔案。這樣我們就可以用註解來幫助我們處理一些固定邏輯的重複性程式碼(如butterknife),提高開發的效率。

通過註解處理器來生成.java 原始檔基本上都會使用javapoet 這個庫,JavaPoet一個是用於產生 .java 原始檔的輔助庫,它可以很方便地幫助我們生成需要的.java 原始檔,下面來看下具體使用方法。

JavaPoet

在使用前需要先引入這個庫,和 AutoService

一樣可以通過AndroidStudio 直接新增,如下:


下面以最簡單的 HelloWorld 例子來看下怎麼使用JavaPoet

先定義一個註解:

/**
 * JavaPoet HelloWorld 例子
 */
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface JPHelloWorld {
}

在定義個註解處理器來處理這個註解:

/**
 * 處理HelloWorld註解.
 */
@AutoService(Processor.class)
public class HelloWorldProcess extends AbstractProcessor {

    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // Filer是個介面,支援通過註解處理器建立新檔案
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement element : annotations) {
            if (element.getQualifiedName().toString().equals(JPHelloWorld.class.getCanonicalName())) {
                // 建立main方法
                MethodSpec main = MethodSpec.methodBuilder("main")
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .returns(void.class)
                        .addParameter(String[].class, "args")
                        .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                        .build();
                // 建立HelloWorld類
                TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                        .addMethod(main)
                        .build();

                try {
                    // 生成 com.example.HelloWorld.java
                    JavaFile javaFile = JavaFile.builder("com.example", helloWorld)
                            .addFileComment(" This codes are generated automatically. Do not modify!")
                            .build();
                    // 生成檔案
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(JPHelloWorld.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

JavaPoet 來生成一個HelloWorld.java檔案之前,我們還必須在 init()方法裡獲取到 Filer,這是一個用來輔助建立檔案的介面,我們生成檔案都通過它來處理。在 process()方法先建立了個 MethodSpec 表示一個方法,再建立一個 TypeSpec 表示一個類並新增上前面建立的方法,最後用 JavaFile 來生成對應的HelloWorld.java 並寫入檔案。

這是最簡單的例子,整個語法結構也很清晰,相信做程式設計的看到這些使用方法都能猜到是做什麼用的,我就沒詳細說了。除了這個例子,Github上還有很多其它示例,如果你想很好地瞭解編譯時註解的使用的話,還是很有必要把每個例子都過一遍,如果不想自己敲貼上複製下很容易的。

在程式碼中使用定義的註解:

@JPHelloWorld
public class MainActivity extends AppCompatActivity{
    // ...
}

重新 Make 下工程就可以看到生成的 HelloWorld.java 檔案了,目錄如下:


可以看到已經成功生成了 HelloWorld.java 檔案,注意生成檔案所在的目錄,現在我們就可以在專案中直接使用這個 java 類了。當然了,這個例子沒有什麼實際的使用價值,你可以參考其它例子來生成你想要的程式碼,用法是很多的。

這裡只寫了個最簡單的例子,沒有深入更詳細的使用方法,等後面有時間再來整理個更詳細的介紹。