注解處理器(APT)是什么
上一篇講完注解,這篇咱們科普一下注解的其中一種用途——注解處理器(APT),文章會(huì)手把手的幫助大家學(xué)會(huì)APT的使用,并使用簡(jiǎn)單的例子來(lái)進(jìn)行練習(xí)。
一、定義
注解處理器(Annotation Processing Tool,簡(jiǎn)稱APT),是JDK提供的工具,用于在編譯階段未生成class之前對(duì)源碼中的注解進(jìn)行掃描和處理。處理方式大部分都是根據(jù)注解的信息生成新的Java代碼與文件。
APT使用相當(dāng)廣泛,EventBus、ARouter、ButterKnife等流行框架都使用了該技術(shù)。
二、生成注解處理器
2.1 創(chuàng)建注解模塊
① 在項(xiàng)目中新建Module,選擇【Java or Kotlin Library】,名字和包名隨意填入,點(diǎn)擊Finish。
② 在模塊中定義注解,注解保留范圍選擇SOURCE即可(因?yàn)锳PT是作用在源碼階段的,生成class之前),當(dāng)然選擇CLASS和RUNTIME也可以,因?yàn)樗麄兌及琒OURCE階段。
我們創(chuàng)建一個(gè)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)建注解處理器模塊
① 在項(xiàng)目中新建Module,選擇【Java or Kotlin Library】,名字和包名隨意填入,點(diǎn)擊Finish。
② 修改新建Module的build.gradle文件,根據(jù)是否使用Kotlin分為兩種情況。
項(xiàng)目不使用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
項(xiàng)目使用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_8
2.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模塊中引入注解處理器
在主項(xiàng)目app模塊的build.gradle中引入注解處理器,使用kapt或annotationProcessor修飾,所以在gradle中看到這兩種修飾的項(xiàng)目就是注解處理器項(xiàng)目了。
dependencies { // 注解模塊 implementation project(":lib-annotation") // 注解處理器模塊,以下二選一 // 使用Kotlin選擇這種 kapt project(":compiler") // 使用Java選擇這種 annotationProcessor project(":compiler") }
2.5 測(cè)試
經(jīng)過(guò)上面的一系列操作,注解處理器已經(jīng)注冊(cè)完成,在其process方法中會(huì)接收到想要處理的注解。
① 在項(xiàng)目中使用@Test注解
@Test(id = 100, desc = "Person類", clazz = Person.class, array = {"111", "aaa", "bbb"}) public class Person { }
② 在注解處理器的init方法中,可以通過(guò)ProcessingEnvironment參數(shù)獲取Messager對(duì)象(可以打印日志),在process方法中獲取到注解后輸出日志查看被注解的類名。(注意:如果打印日志使用Diagnostic.Kind.ERROR,會(huì)中斷構(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對(duì)象 messager = processingEnv.getMessager(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 獲取所有的被Test注解的對(duì)象,無(wú)論是類還是屬性都會(huì)被封裝成Element for (Element element : roundEnv.getElementsAnnotatedWith(Test.class)) { messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>GetAnnotation:" + element.getSimpleName()); } return false; } }
③ 構(gòu)建項(xiàng)目,查看日志,成功獲取到注解注釋過(guò)的類名
三、解析注解
獲取到了注解,我們看一下如何正確的拿到注解內(nèi)的信息,在注解處理器中類、方法、屬性都會(huì)被形容成Element,由于我們定義的@Test只修飾類,所以Element也都是類。
@AutoService(Processor.class) @SupportedAnnotationTypes(Const.TEST_ANNOTATION) @SupportedSourceVersion(SourceVersion.RELEASE_8) public class TestAnnotationProcessor extends AbstractProcessor { private Messager messager; // 這個(gè)是處理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,因?yàn)槲覀冎恍揎楊?,所以拿到的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()會(huì)拋出MirroredTypeException異常 // 處理方式可以通過(guò)捕獲異常后,在異常中獲取類名 String dataClassName; try { dataClassName = card.clazz().getName(); } catch (MirroredTypeException e) { dataClassName = e.getTypeMirror().toString(); } } return true; } }
四、生成代碼
獲取到了注解信息,下一步就是根據(jù)注解生成Java代碼了。但是生成代碼的意義是什么呢?
答:WMRouter路由在獲取到了注解信息之后,會(huì)根據(jù)注解的內(nèi)容生成路由注冊(cè)的代碼。 把一些復(fù)雜的可能寫(xiě)錯(cuò)的冗長(zhǎng)的代碼變成了自動(dòng)生成,避免了人力的浪費(fèi)和錯(cuò)誤的產(chǎn)生。下面是WMRouter生成的代碼:
目前生成Java代碼有兩種方式,原始方式和JavaPoet。原始方式理解即可,咱們使用JavaPoet來(lái)解析注解、生成代碼。
4.1 原始方式
原始方式就是通過(guò)流一行一行的手寫(xiě)代碼。
優(yōu)點(diǎn):可讀性高。
缺點(diǎn):復(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ū)ο笏枷雭?lái)生成.java文件的庫(kù)。
優(yōu)點(diǎn):面向?qū)ο笏枷?、?fù)用性高。
缺點(diǎn):學(xué)習(xí)成本高、可讀性一般。
因?yàn)閷W(xué)習(xí)點(diǎn)比較多,咱們僅對(duì)用到的API進(jìn)行說(shuō)明,其他的可以參考GitHub地址,里面有相當(dāng)全面的教程。
4.2.1 生成代碼
我們先用JavaPoet生成一個(gè)HelloWorld類,下面是我們想要的Java代碼:
package com.example.helloworld; public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); } }
在JavaPoet中使用了面向?qū)ο蟮乃枷?,萬(wàn)物皆對(duì)象,方法和類也變成了對(duì)象。在類中代碼主要被分為了兩塊,一塊是方法(MethodSpec),一塊是類(TypeSpec)。
接下來(lái)我們使用JavaPoet生成這段代碼,比較易懂。
① 先創(chuàng)建main方法的MethodSpec對(duì)象;
② 再創(chuàng)建HelloWorld類的TypeSpec對(duì)象,將main方法傳入。
// 創(chuàng)建main方法的MethodSpec對(duì)象 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對(duì)象,將main方法傳入 TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") // 類名:HelloWorld .addModifiers(Modifier.PUBLIC, Modifier.FINAL) // 類修飾:public final .addMethod(main) // 添加方法main .build(); // 構(gòu)建生成文件,第一個(gè)參數(shù)為包名 JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) .build(); javaFile.writeTo(System.out);
經(jīng)過(guò)以上步驟就可以生成HelloWorld類。
4.2.2 JavaPoet中的自定義類型
上面代碼中會(huì)發(fā)現(xiàn)幾個(gè)奇怪的類型寫(xiě)在字符串中,詳細(xì)的講解可以查看GitHub地址。
$L:值,可以放各種對(duì)象,比如int,Object等。
$S:字符串。
$T:類的引用,會(huì)自動(dòng)導(dǎo)入該類的包,比如new Date()中的Date。
$N:定義好的Method方法名,可以調(diào)用代碼中的其他方法。
4.2.3 各種案例
提供幾個(gè)案例,更好的理解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é)
最后咱們?cè)倏偨Y(jié)一下APT。
APT是JDK提供的工具,用于在編譯階段未生成class之前對(duì)源碼中的注解進(jìn)行掃描和處理。獲取到注解后可以使用原始方法與JavaPoet生成Java代碼。
這樣APT的介紹就結(jié)束了,希望大家讀完這篇文章,會(huì)對(duì)APT有一個(gè)更深入的了解。如果我的文章能給大家?guī)?lái)一點(diǎn)點(diǎn)的福利,那在下就足夠開(kāi)心了。
到此這篇關(guān)于注解處理器(APT)是什么?的文章就介紹到這了,更多相關(guān)APT注解處理器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android系統(tǒng)按鍵音framework流程源碼詳細(xì)解析
這篇文章主要為大家詳細(xì)介紹了android系統(tǒng)按鍵音framework流程源碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08Android自動(dòng)化測(cè)試處理各種彈窗的操作方法
這篇文章主要介紹了Android自動(dòng)化測(cè)試中如何處理各種彈窗,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-07-07通過(guò)Jetpack Compose實(shí)現(xiàn)雙擊點(diǎn)贊動(dòng)畫(huà)效果
這篇文章主要介紹了如何利用Jetpack Compose實(shí)現(xiàn)雙擊點(diǎn)贊動(dòng)畫(huà)效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-01-01Android開(kāi)發(fā)手冊(cè)TextView控件及陰影效果實(shí)現(xiàn)
這篇文章主要為大家介紹了Android開(kāi)發(fā)手冊(cè)TextView控件及陰影效果的實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android Studio 3.x版本 的輸入法遇到的坑及解決方案
前些天把AndroidStudio從2.3.3升級(jí)到3.0,遇到了不少坑,其中一個(gè)巨坑就是輸入中文不提示的問(wèn)題,下面給大家分享Android Studio 3.x版本的輸入法遇到的坑及解決方案,一起看看吧2017-11-11Android實(shí)現(xiàn)搖一搖簡(jiǎn)單功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)搖一搖簡(jiǎn)單功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03Kotlin 使用Lambda來(lái)設(shè)置回調(diào)的操作
這篇文章主要介紹了Kotlin 使用Lambda來(lái)設(shè)置回調(diào)的操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03Android TV 焦點(diǎn)框移動(dòng)的實(shí)現(xiàn)方法
本篇文章主要介紹了Android TV 焦點(diǎn)框移動(dòng)的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06