在上一篇文章《注解實例 - 實現(xiàn)一個簡單的@Autowired運行時注解》中染乌,介紹了如何通過一個運行時注解來實現(xiàn)一個簡單的依賴注入工具队塘。
雖然使用方便,但是運行時注解是有一個硬傷的,那就是使用時需要進行大量掃描和反射操作欧引,會對運行效率造成一定影響漓拾。同時,一些功能需要自動生成代碼來提供拱层,這時候,就需要用到APT了宴咧。當(dāng)然根灯,這里的APT指的不是信息安全中的APT,而是Annotation Processor Tool掺栅,即注解處理器工具烙肺。
本文將介紹如何寫出一個最簡單的APT demo,通過APT處理注解并自動生成文件氧卧,其中關(guān)于注解的知識就不再介紹了桃笙。
首先明確Demo目標(biāo):
- 創(chuàng)建一個類注解
MyAnnotation
以及自定義注解處理器MyProcessor
; - 通過
MyProcessor
自動生成類文件假抄,通過這個類中的函數(shù)可以打印出所有標(biāo)注了MyAnnotation
注解的類怎栽。
一丽猬、準備工作
1.1 新建工程模塊
首先在Android Studio中新建工程。
新建App模塊apt-app
熏瞄,為應(yīng)用主模塊脚祟。
新建Java lib模塊apt-annotation
,為自定義注解模塊强饮。
新建Java lib模塊apt-processor
由桌,為自定義注解處理器模塊。
1.2 添加依賴
在apt-processor
的build.gradle依賴中添加如下依賴:
implementation "com.google.auto.service:auto-service:1.0-rc6"
annotationProcessor "com.google.auto.service:auto-service:1.0-rc6"
implementation 'com.squareup:javapoet:1.13.0'
implementation project(":apt-annotation")
其中google auto service
的作用是輔助自定義的注解處理器的注冊邮丰。
javapoet
的作用是自動生成代碼行您。
在apt-app
模塊中添加如下依賴:
implementation project(":apt-annotation")
annotationProcessor project(":apt-processor")
注意這行annotationProcessor project(":apt-processor")
,是在Java中使用注解處理器剪廉;如果需要在kotlin中使用娃循,則將annotationProcessor
修改為kapt
即可(同時需要在腳本文件最上方添加kapt插件)。
1.3 創(chuàng)建注解
這個注解不需要任何參數(shù)斗蒋,只作為一個標(biāo)記:
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface MyAnnotation {
}
二捌斧、自定義注解處理器
2.1 創(chuàng)建自定義注解處理器類MyProcessor
在apt-processor模塊新建繼承自AbstractProcessor的類MyProcessor
,內(nèi)容如下:
@SupportedOptions("my_param") // 接收外來參數(shù)的key
@SupportedAnnotationTypes("top.littlefogcat.apt_annotation.MyAnnotation") // 支持的注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 支持的Java版本
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> types, RoundEnvironment rEnv) {}
}
其中類上面三個注解依次代表了注解處理器接受外部參數(shù)的key(暫時沒有用到)泉沾、注解處理器支持的注解類型(即1.3中創(chuàng)建的注解全名)捞蚂、注解處理器支持的Java版本。這三個配置也可以通過重寫類中的方法來實現(xiàn)跷究,不過通過注解更加簡便明了姓迅。
2.2 重寫init方法
AbstractProcessor的init
方法提供了一個環(huán)境對象pEnv
,從這個對象中可以得到一系列的工具以及獲取到外部傳入的參數(shù)俊马。這里通過pEnv.getFiler()
獲取到文件工具丁存,以便之后創(chuàng)建文件。
private Filer mFiler;
@Override
public synchronized void init(ProcessingEnvironment pEnv) {
super.init(pEnv);
mFiler = pEnv.getFiler();
}
2.3 重寫process方法
process
是注解處理器的核心方法潭袱,需要在其中實現(xiàn)注解的處理柱嫌。
2.3.1 前置
process方法的參數(shù)
process
方法有兩個參數(shù):Set<? extends TypeElement> types
和RoundEnvironment rEnv
。其中前者表示了需要處理的注解的集合屯换,即創(chuàng)建自定義注解處理器時SupportedAnnotationTypes
中所定義的類;后者則是APT框架提供的查詢程序元素的工具与学,如通過rEnv.getElementsAnnotatedWith
可以查詢到程序中所有標(biāo)注了某注解的類彤悔。
Element
眾所周知,對于靜態(tài)的Java語言(源文件級別)索守,是由包晕窑、類、方法等程序元素組成的卵佛;在對Java源碼的處理中杨赤,各種程序元素對應(yīng)了javax.lang.model.element.Element
接口敞斋。這個概念在之后的處理中會用到。
javapoet
javapoet是一個輔助自動生成java代碼的工具疾牲,可以方便的生成代碼植捎。其中關(guān)鍵類包括:JavaFile(對應(yīng).java文件)、TypeSpec(對應(yīng)類)阳柔、MethodSpec(對應(yīng)方法)焰枢、FieldSpec(對應(yīng)成員變量)、ParameterSpec(對應(yīng)參數(shù))舌剂、AnnotationSpec(對應(yīng)注解)等济锄。之后會使用javapoet來生成代碼。
2.3.2 通過javapoet生成方法
明確目標(biāo)
首先確定需要生成方法的格式霍转。目標(biāo)是這樣的荐绝,即打印所有標(biāo)注了@MyAnnotation
的類的名稱:
public void print() {
System.out.println("以下是標(biāo)注了@MyAnnotation注解的類");
System.out.println("Class1");
System.out.println("Class2");
System.out.println("Class3");
}
創(chuàng)建method builder
在javapoet中,方法對應(yīng)的類是MethodSpec
避消。首先通過建造者模式來創(chuàng)建一個builder同時指定方法名很泊、通過addModifiers
指定可見性、通過returns
指定返回值類型:
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("print") // 函數(shù)名
.addModifiers(Modifier.PUBLIC) // 添加限定符
.returns(void.class); // 返回類型
通過addStatement
方法沾谓,可以在方法體中添加語句委造。這里打印一句話“以下是標(biāo)注了@MyAnnotation注解的類”
:
methodBuilder.addStatement("$T.out.println(\"以下是標(biāo)注了@MyAnnotation注解的類\")", System.class); // 添加語句
掃描所有標(biāo)注了@MyAnnotation的類并打印出來
在2.3.1中已經(jīng)介紹到,可以通過process
方法的第二個參數(shù)獲取所有標(biāo)注了某個標(biāo)記的類均驶。然后再在方法中添加打印語句昏兆,將這些類的名稱打印出來:
// 標(biāo)注了@MyAnnotation的節(jié)點
Set<? extends Element> rootElements = rEnv.getElementsAnnotatedWith(MyAnnotation.class);
// 查詢所有標(biāo)注了@MyAnnotation的類,并打印出來
if (rootElements != null && !rootElements.isEmpty()) {
for (Element element : rootElements) {
String name = element.getSimpleName().toString();
methodBuilder.addStatement("$T.out.println($S)", System.class, name); // 添加打印語句
}
}
完成構(gòu)造
MethodSpec method = methodBuilder.build(); // 完成構(gòu)造
到這里妇穴,目標(biāo)方法就已經(jīng)構(gòu)建完成了爬虱。
2.3.3 通過javapoet生成類
與生成方法類似,生成類也是通過構(gòu)造者模式腾它,并可以通過addMethod
將之前生成的方法添加到這個類中:
// 生成類
TypeSpec myClass = TypeSpec.classBuilder("AptGeneratedClass") // 類名
.addModifiers(Modifier.PUBLIC) // public類
.addMethod(method) // 添加上述方法
.build(); // 構(gòu)造類
這里將生成的類名命名為AptGeneratedClass
跑筝。
2.3.4 生成Java文件
生成Java文件分為兩步,第一步是通過2.3.3中的類對象生成JavaFile
類型的文件對象瞒滴,第二步是通過2.2中獲取的Filer
文件工具將其寫入到.java文件曲梗。
// 生成文件
JavaFile javaFile = JavaFile.builder("top.littlefogcat.apt", myClass) // 包名、類對象
.build();
try {
javaFile.writeTo(mFiler); // 通過文件工具創(chuàng)建文件
} catch (IOException e) {
e.printStackTrace();
}
至此妓忍,就通過APT完成了一個最簡單的可以自動生成文件的注解處理器虏两。
2.4 包含注釋的MyProcessor完整代碼
/**
* 自定義APT類
* <p>
* TypeElement:類元素
* <p>
* 對于Java語言來講,將其看做結(jié)構(gòu)化的語言模型世剖,那么就分為了:
* PackageElement包元素定罢,
* TypeElement類元素,
* TypeParameterElement泛型元素旁瘫,
* VariableElement變量元素
* ExecutableElement可執(zhí)行元素(方法)
* <p>
* 見{@link javax.lang.model.element.Element}
*/
@AutoService(Processor.class)
@SupportedOptions("my_param") // 接收外來參數(shù)的key
@SupportedAnnotationTypes("top.littlefogcat.apt_annotation.MyAnnotation") // 支持的注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 支持的Java版本
public class MyProcessor extends AbstractProcessor {
/*
* 一些工具祖凫,在init方法中通過環(huán)境對象獲取
*/
private Types mTypeUtils;
private Elements mElementUtils;
private Messager mMessager;
private Filer mFiler; // 文件工具
private String mParam;
/**
* 做一些初始化的工作琼蚯,可以通過pEnv參數(shù)獲取一些工具類。
* 同時惠况,通過`SupportedOptions`配置的參數(shù)也可以在這里獲取遭庶。
*
* @param pEnv 環(huán)境對象,提供一些工具
*/
@Override
public synchronized void init(ProcessingEnvironment pEnv) {
super.init(pEnv);
mTypeUtils = pEnv.getTypeUtils();
mElementUtils = pEnv.getElementUtils();
mMessager = pEnv.getMessager();
mFiler = pEnv.getFiler();
mParam = pEnv.getOptions().get("env_param"); // 獲取外界傳入?yún)?shù)
}
/**
* @param types 需要處理的注解集合
* @param rEnv 運行環(huán)境售滤?通過這個對象查詢節(jié)點信息
* @return 處理成功返回true罚拟,否則返回false
*/
@Override
public boolean process(Set<? extends TypeElement> types, RoundEnvironment rEnv) {
if (types == null || types.isEmpty()) {
return false;
}
// 生成一個函數(shù)格式如:
// public void print() {
// System.out.println("以下是標(biāo)注了@MyAnnotation注解的類");
// System.out.println("AnnotedClass1");
// System.out.println("AnnotedClass2");
// }
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("print") // 函數(shù)名
.addModifiers(Modifier.PUBLIC) // 添加限定符
.returns(void.class) // 返回類型
.addStatement("$T.out.println(\"以下是標(biāo)注了@MyAnnotation注解的類\")", System.class); // 添加語句
// 標(biāo)注了@MyAnnotation的節(jié)點
Set<? extends Element> rootElements = rEnv.getElementsAnnotatedWith(MyAnnotation.class);
// 查詢所有標(biāo)注了@MyAnnotation的類,并打印出來
if (rootElements != null && !rootElements.isEmpty()) {
for (Element element : rootElements) {
String name = element.getSimpleName().toString();
methodBuilder.addStatement("$T.out.println($S)", System.class, name);
}
}
MethodSpec method = methodBuilder.build(); // 完成構(gòu)造
// 生成類
TypeSpec myClass = TypeSpec.classBuilder("AptGeneratedClass") // 類名
.addModifiers(Modifier.PUBLIC) // public類
.addMethod(method) // 添加上述方法
.build(); // 構(gòu)造類
// 生成文件
JavaFile javaFile = JavaFile.builder("top.littlefogcat.apt", myClass) // 包名完箩、類型
.build();
try {
javaFile.writeTo(mFiler); // 通過文件工具創(chuàng)建文件
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
/**
* 接受外來參數(shù)赐俗,比如在build.gradle中的javaCompileOptions.annotationProcessorOptions配置
* <p>
* 也可以通過`@SupportedOptions`注解來配置
*/
@Override
public Set<String> getSupportedOptions() {
return Collections.singleton("my_param");
}
/**
* 返回當(dāng)前注解處理器支持的注解類型
* <p>
* 也可以通過`@SupportedAnnotationTypes`注解來配置
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton("top.littlefogcat.apt_annotation.MyAnnotation");
}
/**
* 返回當(dāng)前注解處理器支持的JDK版本
* <p>
* 也可以通過`@SupportedSourceVersion`注解來配置
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
}
三、測試效果
在apt-app
模塊中創(chuàng)建MainActivity弊知,將其添加@MyAnnotation注解阻逮。這里使用的kotlin,注意build.gradle中需要將annotationProcessor
改成kapt
秩彤。
@MyAnnotation
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Build一下叔扼,可以看到AptGeneratedClass.java
已經(jīng)生成完畢了,并且打印出了標(biāo)注了@MyAnnotation注解的類(MainActivity)漫雷。
四瓜富、參考資料
《Android APT 系列 (三):APT 技術(shù)探究》
《JavaPoet - 優(yōu)雅地生成代碼》