注解處理器(APT)是什么
上一篇講完注解,這篇咱們科普一下注解的其中一種用途——注解處理器(APT),文章會手把手的幫助大家學(xué)會APT的使用,并使用簡單的例子來進(jìn)行練習(xí)。
一、定義
注解處理器(Annotation Processing Tool,簡稱APT),是JDK提供的工具,用于在編譯階段未生成class之前對源碼中的注解進(jìn)行掃描和處理。處理方式大部分都是根據(jù)注解的信息生成新的Java代碼與文件。
APT使用相當(dāng)廣泛,EventBus、ARouter、ButterKnife等流行框架都使用了該技術(shù)。
二、生成注解處理器
2.1 創(chuàng)建注解模塊
① 在項目中新建Module,選擇【Java or Kotlin Library】,名字和包名隨意填入,點擊Finish。

② 在模塊中定義注解,注解保留范圍選擇SOURCE即可(因為APT是作用在源碼階段的,生成class之前),當(dāng)然選擇CLASS和RUNTIME也可以,因為他們都包含SOURCE階段。
我們創(chuàng)建一個Test注解,并且包含int、String、Class、String[]四種類型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Test {
int key();
String value() default "";
Class clazz();
String[] array() default {};
}2.2 創(chuàng)建注解處理器模塊
① 在項目中新建Module,選擇【Java or Kotlin Library】,名字和包名隨意填入,點擊Finish。

② 修改新建Module的build.gradle文件,根據(jù)是否使用Kotlin分為兩種情況。
項目不使用Kotlin:
apply plugin: "java-library"
dependencies {
// 注解模塊(必選)
implementation project(':lib-annotation')
// 注解處理器(必選)
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
// 生成代碼方式之一(可選)
implementation 'com.squareup:javapoet:1.13.0'
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8項目使用Kotlin:
apply plugin: "java-library"
apply plugin: "kotlin"
apply plugin: "kotlin-kapt"
dependencies {
// 注解模塊(必選)
implementation project(':lib-annotation')
// 注解處理器(必選)
kapt 'com.google.auto.service:auto-service:1.0-rc7'
implementation 'com.google.auto.service:auto-service:1.0-rc7'
// 生成代碼方式之一(可選)
implementation 'com.squareup:javapoet:1.13.0'
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_82.3 創(chuàng)建注解處理器
在2.2的注解處理器模塊中新建類,繼承自AbstractProcessor類。
使用@AutoService、@SupportedAnnotationTypes、@SupportedSourceVersion注解注釋該類,注解處理器即創(chuàng)建完成,具體如下:
// 讓該類擁有了獲取注解的能力(必選)
@AutoService(Processor.class)
// 設(shè)置該處理器支持哪幾種注解(必選)
// 字符串類型,例:com.kproduce.annotation.TEST
@SupportedAnnotationTypes({Const.CARD_ANNOTATION,Const.TEST_ANNOTATION})
// 源碼版本(可選)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class TestAnnotationProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}2.4 在app模塊中引入注解處理器
在主項目app模塊的build.gradle中引入注解處理器,使用kapt或annotationProcessor修飾,所以在gradle中看到這兩種修飾的項目就是注解處理器項目了。
dependencies {
// 注解模塊
implementation project(":lib-annotation")
// 注解處理器模塊,以下二選一
// 使用Kotlin選擇這種
kapt project(":compiler")
// 使用Java選擇這種
annotationProcessor project(":compiler")
}2.5 測試
經(jīng)過上面的一系列操作,注解處理器已經(jīng)注冊完成,在其process方法中會接收到想要處理的注解。
① 在項目中使用@Test注解
@Test(id = 100, desc = "Person類", clazz = Person.class, array = {"111", "aaa", "bbb"})
public class Person {
}② 在注解處理器的init方法中,可以通過ProcessingEnvironment參數(shù)獲取Messager對象(可以打印日志),在process方法中獲取到注解后輸出日志查看被注解的類名。(注意:如果打印日志使用Diagnostic.Kind.ERROR,會中斷構(gòu)建)
@AutoService(Processor.class)
@SupportedAnnotationTypes(Const.TEST_ANNOTATION)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class TestAnnotationProcessor extends AbstractProcessor {
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 獲取Messager對象
messager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 獲取所有的被Test注解的對象,無論是類還是屬性都會被封裝成Element
for (Element element : roundEnv.getElementsAnnotatedWith(Test.class)) {
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>GetAnnotation:" + element.getSimpleName());
}
return false;
}
}③ 構(gòu)建項目,查看日志,成功獲取到注解注釋過的類名

三、解析注解
獲取到了注解,我們看一下如何正確的拿到注解內(nèi)的信息,在注解處理器中類、方法、屬性都會被形容成Element,由于我們定義的@Test只修飾類,所以Element也都是類。
@AutoService(Processor.class)
@SupportedAnnotationTypes(Const.TEST_ANNOTATION)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class TestAnnotationProcessor extends AbstractProcessor {
private Messager messager;
// 這個是處理Element的工具
private Elements elementTool;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
elementTool = processingEnv.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 拿到被Test修飾的Element,因為我們只修飾類,所以拿到的Element都是類
for (Element element : roundEnv.getElementsAnnotatedWith(Test.class)) {
// ===============獲取當(dāng)前被修飾的類的信息===============
// 獲取包名,例:com.kproduce.androidstudy.test
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();
// 獲取類名,例:Person
String className = element.getSimpleName().toString();
// 拼裝成文件名,例:com.kproduce.androidstudy.test.Person
String fileName = packageName + Const.DOT + className;
// ===============解析注解===============
// 獲取注解
Test card = element.getAnnotation(Test.class);
// 注解中的int值
int id = card.id();
// 注解中的String
String desc = card.desc();
// 注解中的數(shù)組[]
String[] array = card.array();
// 獲取類有比較奇葩的坑,需要特別注意!
// 在注解中拿Class然后調(diào)用getName()會拋出MirroredTypeException異常
// 處理方式可以通過捕獲異常后,在異常中獲取類名
String dataClassName;
try {
dataClassName = card.clazz().getName();
} catch (MirroredTypeException e) {
dataClassName = e.getTypeMirror().toString();
}
}
return true;
}
}
四、生成代碼
獲取到了注解信息,下一步就是根據(jù)注解生成Java代碼了。但是生成代碼的意義是什么呢?
答:WMRouter路由在獲取到了注解信息之后,會根據(jù)注解的內(nèi)容生成路由注冊的代碼。 把一些復(fù)雜的可能寫錯的冗長的代碼變成了自動生成,避免了人力的浪費和錯誤的產(chǎn)生。下面是WMRouter生成的代碼:

目前生成Java代碼有兩種方式,原始方式和JavaPoet。原始方式理解即可,咱們使用JavaPoet來解析注解、生成代碼。
4.1 原始方式
原始方式就是通過流一行一行的手寫代碼。
優(yōu)點:可讀性高。
缺點:復(fù)用性差。
咱們看一下EventBus的源碼就能更深刻的理解什么是原始方式:
// 截取EventBusAnnotationProcessor.java中的片段
private void createInfoIndexFile(String index) {
BufferedWriter writer = null;
try {
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
int period = index.lastIndexOf('.');
String myPackage = period > 0 ? index.substring(0, period) : null;
String clazz = index.substring(period + 1);
writer = new BufferedWriter(sourceFile.openWriter());
if (myPackage != null) {
writer.write("package " + myPackage + ";\n\n");
}
writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
writer.write("import java.util.HashMap;\n");
writer.write("import java.util.Map;\n\n");
writer.write("/** This class is generated by EventBus, do not edit. */\n");
writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
writer.write(" private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
writer.write(" static {\n");
writer.write(" SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
writeIndexLines(writer, myPackage);
writer.write(" }\n\n");
writer.write(" private static void putIndex(SubscriberInfo info) {\n");
writer.write(" SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
writer.write(" }\n\n");
writer.write(" @Override\n");
writer.write(" public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
writer.write(" SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
writer.write(" if (info != null) {\n");
writer.write(" return info;\n");
writer.write(" } else {\n");
writer.write(" return null;\n");
writer.write(" }\n");
writer.write(" }\n");
writer.write("}\n");
} catch (IOException e) {
throw new RuntimeException("Could not write source for " + index, e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
//Silent
}
}
}
}
4.2 JavaPoet
JavaPoet是使用Java的API和面向?qū)ο笏枷雭砩?java文件的庫。
優(yōu)點:面向?qū)ο笏枷搿?fù)用性高。
缺點:學(xué)習(xí)成本高、可讀性一般。
因為學(xué)習(xí)點比較多,咱們僅對用到的API進(jìn)行說明,其他的可以參考GitHub地址,里面有相當(dāng)全面的教程。
4.2.1 生成代碼
我們先用JavaPoet生成一個HelloWorld類,下面是我們想要的Java代碼:
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}在JavaPoet中使用了面向?qū)ο蟮乃枷?,萬物皆對象,方法和類也變成了對象。在類中代碼主要被分為了兩塊,一塊是方法(MethodSpec),一塊是類(TypeSpec)。

接下來我們使用JavaPoet生成這段代碼,比較易懂。
① 先創(chuàng)建main方法的MethodSpec對象;
② 再創(chuàng)建HelloWorld類的TypeSpec對象,將main方法傳入。
// 創(chuàng)建main方法的MethodSpec對象
MethodSpec main = MethodSpec.methodBuilder("main") // 方法名:main
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) // 方法修飾:public static
.returns(void.class) // 返回類型 void
.addParameter(String[].class, "args") // 參數(shù):String[] args
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") // 內(nèi)容System.out.println("Hello, JavaPoet!");
.build();
// 創(chuàng)建HelloWorld類的TypeSpec對象,將main方法傳入
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") // 類名:HelloWorld
.addModifiers(Modifier.PUBLIC, Modifier.FINAL) // 類修飾:public final
.addMethod(main) // 添加方法main
.build();
// 構(gòu)建生成文件,第一個參數(shù)為包名
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);經(jīng)過以上步驟就可以生成HelloWorld類。
4.2.2 JavaPoet中的自定義類型
上面代碼中會發(fā)現(xiàn)幾個奇怪的類型寫在字符串中,詳細(xì)的講解可以查看GitHub地址。
$L:值,可以放各種對象,比如int,Object等。
$S:字符串。
$T:類的引用,會自動導(dǎo)入該類的包,比如new Date()中的Date。
$N:定義好的Method方法名,可以調(diào)用代碼中的其他方法。
4.2.3 各種案例
提供幾個案例,更好的理解JavaPoet,詳細(xì)的講解可以查看GitHub地址。
① 循環(huán)
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
// JavaPoet方式 1
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for (int i = 0; i < 10; i++)")
.addStatement("total += i")
.endControlFlow()
.build();
// JavaPoet方式 2
MethodSpec main = MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();② ArrayList
package com.example.helloworld;
import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;
public final class HelloWorld {
List<Hoverboard> beyond() {
List<Hoverboard> result = new ArrayList<>();
result.add(new Hoverboard());
result.add(new Hoverboard());
result.add(new Hoverboard());
return result;
}
}
// JavaPoet方式
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);
MethodSpec beyond = MethodSpec.methodBuilder("beyond")
.returns(listOfHoverboards)
.addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("return result")
.build();③ 屬性
public class HelloWorld {
private final String android;
private final String robot;
}
// JavaPoet方式
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();總結(jié)
最后咱們再總結(jié)一下APT。
APT是JDK提供的工具,用于在編譯階段未生成class之前對源碼中的注解進(jìn)行掃描和處理。獲取到注解后可以使用原始方法與JavaPoet生成Java代碼。
這樣APT的介紹就結(jié)束了,希望大家讀完這篇文章,會對APT有一個更深入的了解。如果我的文章能給大家?guī)硪稽c點的福利,那在下就足夠開心了。
到此這篇關(guān)于注解處理器(APT)是什么?的文章就介紹到這了,更多相關(guān)APT注解處理器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android12?SD如何動態(tài)申請讀寫權(quán)限
這篇文章主要給大家介紹了關(guān)于android12?SD如何動態(tài)申請讀寫權(quán)限的相關(guān)資料,從Android?6.0開始,權(quán)限不再是在manifest?件中粘貼?下即可,這時候權(quán)限也正式?進(jìn)?家的視野,需要的朋友可以參考下2023-07-07
windows10安裝adb/fastboot驅(qū)動超詳細(xì)圖文教程
這篇文章主要介紹了windows10安裝adb/fastboot超詳細(xì)圖文教程,安裝方法也很簡單,只要adb安裝成功,fastboot就安裝好了,文中給大家介紹了問題分析及解決方法,需要的朋友可以參考下2023-01-01
Android開發(fā)中ImageLoder加載網(wǎng)絡(luò)圖片時將圖片設(shè)置為ImageView背景的方法
這篇文章主要介紹了Android開發(fā)中ImageLoder加載網(wǎng)絡(luò)圖片時將圖片設(shè)置為ImageView背景的方法,涉及Android ImageView圖片加載及背景設(shè)置相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
Android自定義View實現(xiàn)天氣預(yù)報折線圖
這篇文章主要為大家詳細(xì)介紹了Android自定義View實現(xiàn)天氣預(yù)報折線圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-09-09
Android App開發(fā)的自動化測試框架UI Automator使用教程
UI Automator為Android程序的UI開發(fā)提供了測試環(huán)境,這里我們就來看一下Android App開發(fā)的自動化測試框架UI Automator使用教程,需要的朋友可以參考下2016-07-07

