上一篇咱們按照思惟導圖,介紹了註解的基礎知識,如何定義一個註解,提示性註解,運行時註解的寫法和用法。沒有看過第一篇,又對註解知識相對陌生的同窗,建議先食用第一篇。本篇將重點介紹編譯期註解,自動生成Java文件相關內容。第一篇傳送門:java
Android Annotation-讓你的代碼更加優雅(一)android
照例,這裏先給出本篇的學習導圖。方便你們掌握學習大綱。本章照例會先給出一些用來處理編譯期註解的基礎類和方法,而後經過一些具體的例子學習如何利用編譯期註解來實現一些便捷功能。本篇的食用時間可能稍長,建議收藏後慢慢食用。git
JavaPoet是square公司的開源庫,傳送門見下面。從名字就能夠看出,Java詩人,即JavaPoet是一個經過註解生成java文件的庫。咱們能夠利用註解,運用JavaPoet來生成一些重複的模板代碼。從而大大提升咱們編程效率。像咱們熟知的ButterKnife,就是經過這種方法來簡化代碼編寫的。在JavaPoet使用過程當中,也須要用到一些Java API,咱們會在後文一併講解。github
使用時,引入依賴就能夠了:json
compile 'com.squareup:javapoet:1.7.0'
複製代碼
瞭解編譯原理的同窗都知道,在編譯器眼中,代碼文件其實就是按必定語法編寫的結構化數據。編譯器在處理Java文件時,也是按照既定的語法,分析Java文件的結構化數據。結構化數據就是咱們平常編寫Java文件時用到的基本元素。在Java中,對於編譯器來講代碼中的元素結構是基本不變的,例如組成代碼的基本元素包括包、類、函數、字段、變量等,JDK爲這些元素定義了一個基類,也就是Element
,咱們用Element
及其子類來表示這些基本元素,它共用5個子類:bash
類名 | 表達的元素 |
---|---|
PackageElement | 表示一個包程序元素,能夠獲取到包名等 |
TypeElement | 表示一個類或接口程序元素 |
VariableElement | 表示一個字段、enum 常量、方法或構造方法參數、局部變量、類成員變量或異常參數 |
ExecutableElement | 表示某個類或接口的方法、構造方法或初始化程序(靜態或實例),包括註解類型元素 |
TypeParameterElement | 表示通常類、接口、方法或構造方法元素的泛型參數 |
經過一個例子來明確一下:app
package com.xm.test; // 包名,PackageElement
public class Test< // 類名,TypeElement T // 泛型參數,TypeParameterElement > {
private int a; // 成員變量,VariableElement
private Test other; // 成員變量,VariableElement
public Test () {} // 成員方法,ExecuteableElement
public void setA ( // 成員方法,ExecuteableElement int newA // 方法參數,VariableElement ) {
String test; // 局部變量,VariableElement
}
}
複製代碼
當編譯器操做Java文件中的元素時,就是經過上面這些類來進行操做的。即咱們想經過JavaPoet來生成Java文件時,就可使用這些子類來表達結構化程序的元素。任何一個Element
類對象,均可以根據實際狀況,強轉成對應的子類。而Element
類,其實是一個接口,它定義了一套方法,咱們來一塊兒看一下。ide
public interface Element extends AnnotatedConstruct {
/** * 返回此元素定義的類型 * 例如,對於通常類元素 Clazz<P extends People>,返回參數化類型 Clazz<P> */
TypeMirror asType();
/** * 返回此元素的種類:包、類、接口、方法、字段...,以下枚舉值 * PACKAGE, ENUM, CLASS, ANNOTATION_TYPE, INTERFACE, ENUM_CONSTANT, FIELD, PARAMETER, LOCAL_VARIABLE, EXCEPTION_PARAMETER, * METHOD, CONSTRUCTOR, STATIC_INIT, INSTANCE_INIT, TYPE_PARAMETER, OTHER, RESOURCE_VARIABLE; */
ElementKind getKind();
/** * 返回此元素的修飾符,以下枚舉值 * PUBLIC, PROTECTED, PRIVATE, ABSTRACT, DEFAULT, STATIC, FINAL, * TRANSIENT, VOLATILE, SYNCHRONIZED, NATIVE, STRICTFP; */
Set<Modifier> getModifiers();
/** * 返回此元素的簡單名稱,例如 * 類型元素 java.util.Set<E> 的簡單名稱是 "Set"; * 若是此元素表示一個未指定的包,則返回一個空名稱; * 若是它表示一個構造方法,則返回名稱 "<init>"; * 若是它表示一個靜態初始化程序,則返回名稱 "<clinit>"; * 若是它表示一個匿名類或者實例初始化程序,則返回一個空名稱 */
Name getSimpleName();
/** * 返回封裝此元素的最裏層元素。 * 若是此元素的聲明在詞法上直接封裝在另外一個元素的聲明中,則返回那個封裝元素; * 若是此元素是頂層類型,則返回它的包; * 若是此元素是一個包,則返回 null; * 若是此元素是一個泛型參數,則返回 null. */
Element getEnclosingElement();
/** * 返回此元素直接封裝的子元素 */
List<? extends Element> getEnclosedElements();
boolean equals(Object var1);
int hashCode();
/** * 返回直接存在於此元素上的註解 * 要得到繼承的註解,可以使用 getAllAnnotationMirrors */
List<? extends AnnotationMirror> getAnnotationMirrors();
/** * 返回此元素針對指定類型的註解(若是存在這樣的註解),不然返回 null。註解能夠是繼承的,也能夠是直接存在於此元素上的 */
<A extends Annotation> A getAnnotation(Class<A> annotationType);
<R, P> R accept(ElementVisitor<R, P> var1, P var2);
}
複製代碼
APT,說其是詩人的大腦,是由於咱們整個代碼生成任務的核心,須要用到註解的處理器提供的方法和入口。在整個流程的核心部分,都是由APT來實現和控制的。APT是一種處理註解的工具,確切的說它是javac的一個工具,它用來在編譯時掃描和處理註解,一個註解的註解處理器,以java代碼(或者編譯過的字節碼)做爲輸入,生成.java文件做爲輸出,核心是交給本身定義的處理器去處理。實際上,APT在編譯期留出了一個供咱們編程的一套模板接口。咱們經過實現處理器中的方法,就能夠編寫本身的註解處理流程了。函數
每一個自定義的註解處理器,都要繼承虛處理器AbstractProcessor
,來實現其幾個關鍵方法。
虛處理器AbstractProcessor
的幾個關鍵方法:
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion() { }
}
複製代碼
當咱們實現自定義的註解處理器時,上述的這幾個方法,是必需要實現的。下面重點介紹一下這四個方法:
init(ProcessingEnvironment env)
:每個註解處理器類都必須有一個空的構造函數。然而,這裏有一個特殊的init()方法,它會被註解處理工具調用,並輸入ProcessingEnviroment參數。ProcessingEnviroment提供不少有用的工具類,如Elements, Types和Filer。
process(Set<? extends TypeElement> annotations, RoundEnvironment env)
:這至關於每一個處理器的主函數main()。你在這裏寫你的掃描、評估和處理註解的代碼,以及生成Java文件。輸入參數RoundEnviroment,可讓你查詢出包含特定註解的被註解元素。這是一個布爾值,代表註解是否已經被處理器處理完成,官方原文whether or not the set of annotations are claimed by this processor,一般在處理出現異常直接返回false、處理完成返回true。
getSupportedAnnotationTypes()
:必需要實現;用來表示這個註解處理器是註冊給哪一個註解的。返回值是一個字符串的集合,包含本處理器想要處理的註解類型的合法全稱。
getSupportedSourceVersion()
:用來指定你使用的Java版本。一般這裏返回SourceVersion.latestSupported(),你也可使用SourceVersion_RELEASE_六、七、8註冊處理器版本。
因爲註解處理器是javac的工具,所以咱們必須將自定義的處理器註冊到javac中,方法是咱們須要提供一個.jar文件,打包你的註解處理器到此文件中,而且在jar中,須要打包一個特定的文件 javax.annotation.processing.Processor到META-INF/services路徑下 。而這一切都是極爲繁瑣的。幸虧谷歌給咱們開發了AutoService註解,你只須要引入這個依賴,而後在你的處理器類上加上註解:
@AutoService(Processor.class)
複製代碼
而後咱們就能夠自動生成文件,並打包進jar中。省去了不少麻煩事兒。
那麼上面咱們介紹完處理器相關內容,下面咱們再來看一看APT還爲咱們提供了哪些其它工具。
這四個工具,咱們經過在AbstractProcessor的實現類中,經過ProcessingEnvironment便可得到:
private Filer mFiler;
private Elements mElementUtils;
private Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mElementUtils = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
mFiler = processingEnv.getFiler();
}
複製代碼
從名字看得出來,與文件相關的操做會用到。通常配合JavaPoet來生成Java文件
它提供給註解處理器一個報告錯誤、警告信息的途徑。當咱們自定義的註解處理器運行時報錯時,那麼運行註解處理器的JVM也會崩潰,打印出一些不容易被應用開發者讀懂的日誌信息。這時,咱們能夠藉助Messager輸出一些調試信息,以更直觀的方式提示程序運行的錯誤。
Types是一個用來操做TypeMirror的工具。TypeMirror是Element中經過adType()方法獲得的一個對象。它保存了元素的具體信息,好比Element是一個類,那麼其成員詳細信息就保存在TypeMirror中。
Elements是一個用來處理Element的工具。這裏不詳細展開了。用到的時候會提到。
JavaPoet爲咱們提供了編譯期經過操做Java文件結構元素,依據註解生成Java文件的便捷方法。那麼如何來生成呢?咱們先有必要來了解一下JavaPoet爲咱們提供了哪些工具。
這些用來表達Java文件元素的類,其實和上面說的Element有殊途同歸之妙。如今無法具體理解不要緊,後面有例子。
類名 | 含義 |
---|---|
MethodSpec | 表明一個構造函數或方法聲明 |
TypeSpec | 表明一個類,接口,或者枚舉聲明 |
FieldSpec | 表明一個成員變量,一個字段聲明 |
ParameterSpec | 表明一個參數,可用來生成參數 |
AnnotationSpec | 表明一個註解 |
JavaFile | 包含一個頂級類的Java文件 |
咱們先來總體看一個簡單的例子,而後再拓展到各類狀況。
假如咱們定義了以下一個註解,運用上一篇咱們學過的知識:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Xnpe {
String value();
}
複製代碼
接下來實現註解處理器:
@AutoService(Processor.class)
public class XnpeProcess extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement element : annotations) {
if (element.getQualifiedName().toString().equals(Xnpe.class.getCanonicalName())) {
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();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
try {
JavaFile javaFile = JavaFile.builder("com.xm", 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(Xnpe.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
複製代碼
這裏須要一點耐心了,乍看起來有點多,但實際上比較簡單。這裏咱們總結出實現自定義註解處理器的幾個關鍵步驟:
本例中,咱們先來重點看第二條,即四個大方法的實現。重點在處理方法上,即process()方法。咱們拿出其中的核心部分作一個講解。
MethodSpec main = MethodSpec.methodBuilder("main") //MethodSpec固然是methodBuilder,即建立方法。
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)//增長限定符
.returns(void.class) //指定返回值類型
.addParameter(String[].class, "args") //指定方法的參數
.addStatement("$T.out.println($S)", System.class, "Hello, I am Poet!")//添加邏輯代碼。
.build(); //建立
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") //TypeSpec構建Class
.addModifiers(Modifier.PUBLIC, Modifier.FINAL) //增長限定符
.addMethod(main) //將剛纔建立的main方法添加進類中。
.build(); //建立
複製代碼
是否是流程上很容易理解。MethodSpec是用來生成方法的,詳細解釋可參加代碼上的註釋。
細心的你也許注意到了,代碼中有些$T等字樣的東東,這個又是什麼呢?下面咱們經過幾個小例子,一方面來了解一下Poet中的一些佔位符,另外一方面也熟悉一下經常使用的方法。
addCode
與addStatement
用來增長代碼MethodSpec main = MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();
複製代碼
生成的是
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
複製代碼
addCode
用於增長極簡代碼。即代碼中僅包含純Java基礎類型和運算。addStatement
用於增長一些須要import方法的代碼。如上面的.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
就須要使用.addStatement
來聲明。流控方法主要用來實現一些流控代碼的添加,比上面的add方法看着美觀一點。好比上面的代碼,能夠改寫爲:
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for (int i = 0; i < 10; i++)")
.addStatement("total += i")
.endControlFlow()
.build();
複製代碼
private MethodSpec computeRange(String name, int from, int to, String op) {
return MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 0")
.beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
.addStatement("result = result $L i", op)
.endControlFlow()
.addStatement("return result")
.build();
}
複製代碼
當咱們傳參調用時,coputeRange("test", 0, 10, "+")
它能生成的代碼以下:
int test(){
int result = 0;
for(int i = 0; i < 10; i++) {
result = result + i;
}
return result;
}
複製代碼
這個比較容易理解,這裏就不贅述了。
MethodSpec today = MethodSpec.methodBuilder("today")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build(); //建立today方法
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(today)
.build(); //建立HelloWorld類
JavaFile javaFile = JavaFile.builder("com.xm.helloworld", helloWorld).build();
javaFile.writeTo(System.out);//寫java文件
複製代碼
生成的代碼以下,咱們看到,它會自動導入所需的包。這也是咱們使用佔位符的好處,也是使用JavaPoet的一大好處。
package com.xm.helloworld;
import java.util.Date;
public final class HelloWorld {
Date today() {
return new Date();
}
}
複製代碼
若是咱們想要導入本身寫的類怎麼辦?上面的例子是傳入系統的class,這裏也提供一種方式,經過ClassName.get(」類的路徑」,」類名「),結合$T能夠生成
ClassName testClass = ClassName.get("com.xm", "TestClass");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOftestClasses = ParameterizedTypeName.get(list, testClass);
MethodSpec xNpe = MethodSpec.methodBuilder("xNpe")
.returns(listOftestClasses)
.addStatement("$T result = new $T<>()", listOftestClasses, arrayList)
.addStatement("result.add(new $T())", testClass)
.addStatement("result.add(new $T())", testClass)
.addStatement("result.add(new $T())", testClass)
.addStatement("return result")
.build();
複製代碼
生成的代碼以下:
package com.xm.helloworld;
import com.xm.TestClass;
import java.util.ArrayList;
import java.util.List;
public final class HelloWorld {
List<TestClass> xNpe() {
List<TestClass> result = new ArrayList<>();
result.add(new TestClass());
result.add(new TestClass());
result.add(new TestClass());
return result;
}
}
複製代碼
Javapoet 一樣支持import static,經過addStaticImport
來添加:
JavaFile.builder("com.xm.helloworld", hello)
.addStaticImport(TestClass, "START")
.addStaticImport(TestClass2, "*")
.addStaticImport(Collections.class, "*")
.build();
複製代碼
一般指咱們本身生成的方法名或者變量名等等。好比這樣的代碼:
public String byteToHex(int b) {
char[] result = new char[2];
result[0] = hexDigit((b >>> 4) & 0xf);
result[1] = hexDigit(b & 0xf);
return new String(result);
}
public char hexDigit(int i) {
return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
複製代碼
這個例子中,咱們在byteToHex中須要調用到hexDigit方法,咱們就能夠用$N來表示這種引用。而後經過傳遞方法名,達到這種調用語句的生成。
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
.addParameter(int.class, "i")
.returns(char.class)
.addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
.build();
MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
.addParameter(int.class, "b")
.returns(String.class)
.addStatement("char[] result = new char[2]")
.addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
.addStatement("result[1] = $N(b & 0xf)", hexDigit)
.addStatement("return new String(result)")
.build();
複製代碼
咱們在定義方法時,也要對方法增長一些修飾符,如Modifier.ABSTRACT
。能夠經過addModifiers()
方法:
MethodSpec test = MethodSpec.methodBuilder("test")
.addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addMethod(test)
.build();
複製代碼
將會生成以下代碼:
public abstract class HelloWorld {
protected abstract void test();
}
複製代碼
構造器只不過是一個特殊的方法,所以可使用MethodSpec
來生成構造器方法。使用constrctorBuilder
來生成:
MethodSpec flux = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "greeting")
.addStatement("this.$N = $N", "greeting", "greeting")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(flux)
.build();
複製代碼
將會生成代碼:
public class HelloWorld {
private final String greeting;
public HelloWorld(String greeting) {
this.greeting = greeting;
}
}
複製代碼
參數也有本身的一個專用類ParameterSpec
,咱們可使用ParameterSpec.builder()
來生成參數,而後MethodSpec的addParameter去使用,這樣更加優雅。
ParameterSpec android = ParameterSpec.builder(String.class, "android")
.addModifiers(Modifier.FINAL)
.build();
MethodSpec welcomeOverlords = MethodSpec.methodBuilder("test")
.addParameter(android)
.addParameter(String.class, "robot", Modifier.FINAL)
.build();
複製代碼
生成的代碼
void test(final String android, final String robot) {
}
複製代碼
可使用FieldSpec去聲明字段,而後加到類中:
FieldSpec android = FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(android)
.addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
.build();
複製代碼
生成代碼:
public class HelloWorld {
private final String android;
private final String robot;
}
複製代碼
一般Builder能夠更加詳細的建立字段的內容,好比javadoc、annotations或者初始化字段參數等,如:
FieldSpec android = FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer("$S + $L", "Pie v.", 9.0)//初始化賦值
.build();
複製代碼
對應生成的代碼:
private final String android = "Pie v." + 9.0;
複製代碼
接口方法必須是PUBLIC ABSTRACT而且接口字段必須是PUBLIC STATIC FINAL ,使用TypeSpec.interfaceBuilder
以下:
TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(FieldSpec.builder(String.class, "KEY_START")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", "start")
.build())
.addMethod(MethodSpec.methodBuilder("beep")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.build())
.build();
複製代碼
生成的代碼以下:
public interface HelloWorld {
String KEY_START = "start";
void beep();
}
複製代碼
使用TypeSpec.enumBuilder
來建立,使用addEnumConstant
來添加枚舉值:
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build();
複製代碼
生成的代碼
public enum Roshambo {
ROCK,
SCISSORS,
PAPER
}
複製代碼
須要使用Type.anonymousInnerClass("")
,一般可使用$L佔位符來指代:
TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
.addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
.addMethod(MethodSpec.methodBuilder("compare")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "a")
.addParameter(String.class, "b")
.returns(int.class)
.addStatement("return $N.length() - $N.length()", "a", "b")
.build())
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addMethod(MethodSpec.methodBuilder("sortByLength")
.addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
.addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
.build())
.build();
複製代碼
生成代碼:
void sortByLength(List<String> strings) {
Collections.sort(strings, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
}
複製代碼
定義匿名內部類的一個特別棘手的問題是參數的構造。在上面的代碼中咱們傳遞了不帶參數的空字符串。TypeSpec.anonymousClassBuilder("")
。
註解使用起來比較簡單,經過addAnnotation就能夠添加:
MethodSpec toString = MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.returns(String.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "Hello XiaoMing")
.build();
複製代碼
生成代碼
@Override
public String toString() {
return "Hello XiaoMing";
}
複製代碼
經過AnnotationSpec.builder()
能夠對註解設置屬性:
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(AnnotationSpec.builder(Headers.class)
.addMember("accept", "$S", "application/json; charset=utf-8")
.addMember("userAgent", "$S", "Square Cash")
.build())
.addParameter(LogRecord.class, "logRecord")
.returns(LogReceipt.class)
.build();
複製代碼
代碼生成以下
@Headers(
accept = "application/json; charset=utf-8",
userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);
複製代碼
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(AnnotationSpec.builder(HeaderList.class)
.addMember("value", "$L", AnnotationSpec.builder(Header.class)
.addMember("name", "$S", "Accept")
.addMember("value", "$S", "application/json; charset=utf-8")
.build())
.addMember("value", "$L", AnnotationSpec.builder(Header.class)
.addMember("name", "$S", "User-Agent")
.addMember("value", "$S", "Square Cash")
.build())
.build())
.addParameter(LogRecord.class, "logRecord")
.returns(LogReceipt.class
.build
複製代碼
生成Java文件,咱們須要用到上文提到的Filer
和Elements
。注意下面這段代碼,重要的是包名,類名的指定。這裏生成的文件名,通常會遵循某個約定,以便事先寫好反射代碼。
//獲取待生成文件的包名
public String getPackageName(TypeElement type) {
return mElementUtils.getPackageOf(type).getQualifiedName().toString();
}
//獲取待生成文件的類名
private static String getClassName(TypeElement type, String packageName) {
int packageLen = packageName.length() + 1;
return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
}
//生成文件
private void writeJavaFile() {
String packageName = getPackageName(mClassElement);
String className = getClassName(mClassElement, packageName);
ClassName bindClassName = ClassName.get(packageName, className);
TypeSpec finderClass = TypeSpec.classBuilder(bindClassName.simpleName() + "$$Injector")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(TypeUtil.INJECTOR,
TypeName.get(mClassElement.asType())))
.addMethod(methodBuilder.build())
.build();
//使用JavaFile的builder來生成java文件
JavaFile.builder(packageName, finderClass).build().writeTo(mFiler);
}
複製代碼
經過兩篇的學習,咱們熟悉了Java註解的用途,寫法,以及如何用它爲咱們的編碼或程序服務。本篇羅列了不少具體的例子,但願能覆蓋到平常你們使用的方方面面,你們也能夠收藏本文,在使用JavaPoet的時候進行參照。
小銘出品,必屬精品
歡迎關注xNPE技術論壇,更多原創乾貨每日推送。