在上一篇文章《注解实例 - 实现一个简单的@Autowired运行时注解》中,介绍了如何通过一个运行时注解来实现一个简单的依赖注入工具。
虽然使用方便,但是运行时注解是有一个硬伤的,那就是使用时需要进行大量扫描和反射操作,会对运行效率造成一定影响。同时,一些功能需要自动生成代码来提供,这时候,就需要用到APT了。当然,这里的APT指的不是信息安全中的APT,而是Annotation Processor Tool,即注解处理器工具。
本文将介绍如何写出一个最简单的APT demo,通过APT处理注解并自动生成文件,其中关于注解的知识就不再介绍了。
首先明确Demo目标:
- 创建一个类注解
MyAnnotation
以及自定义注解处理器MyProcessor
; - 通过
MyProcessor
自动生成类文件,通过这个类中的函数可以打印出所有标注了MyAnnotation
注解的类。
一、准备工作
1.1 新建工程模块
首先在Android Studio中新建工程。
新建App模块apt-app
,为应用主模块。
新建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 创建注解
这个注解不需要任何参数,只作为一个标记:
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface MyAnnotation {
}
二、自定义注解处理器
2.1 创建自定义注解处理器类MyProcessor
在apt-processor模块新建继承自AbstractProcessor的类MyProcessor
,内容如下:
@SupportedOptions("my_param") // 接收外来参数的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) {}
}
其中类上面三个注解依次代表了注解处理器接受外部参数的key(暂时没有用到)、注解处理器支持的注解类型(即1.3中创建的注解全名)、注解处理器支持的Java版本。这三个配置也可以通过重写类中的方法来实现,不过通过注解更加简便明了。
2.2 重写init方法
AbstractProcessor的init
方法提供了一个环境对象pEnv
,从这个对象中可以得到一系列的工具以及获取到外部传入的参数。这里通过pEnv.getFiler()
获取到文件工具,以便之后创建文件。
private Filer mFiler;
@Override
public synchronized void init(ProcessingEnvironment pEnv) {
super.init(pEnv);
mFiler = pEnv.getFiler();
}
2.3 重写process方法
process
是注解处理器的核心方法,需要在其中实现注解的处理。
2.3.1 前置
process方法的参数
process
方法有两个参数:Set<? extends TypeElement> types
和RoundEnvironment rEnv
。其中前者表示了需要处理的注解的集合,即创建自定义注解处理器时SupportedAnnotationTypes
中所定义的类;后者则是APT框架提供的查询程序元素的工具,如通过rEnv.getElementsAnnotatedWith
可以查询到程序中所有标注了某注解的类。
Element
众所周知,对于静态的Java语言(源文件级别),是由包、类、方法等程序元素组成的;在对Java源码的处理中,各种程序元素对应了javax.lang.model.element.Element
接口。这个概念在之后的处理中会用到。
javapoet
javapoet是一个辅助自动生成java代码的工具,可以方便的生成代码。其中关键类包括:JavaFile(对应.java文件)、TypeSpec(对应类)、MethodSpec(对应方法)、FieldSpec(对应成员变量)、ParameterSpec(对应参数)、AnnotationSpec(对应注解)等。之后会使用javapoet来生成代码。
2.3.2 通过javapoet生成方法
明确目标
首先确定需要生成方法的格式。目标是这样的,即打印所有标注了@MyAnnotation
的类的名称:
public void print() {
System.out.println("以下是标注了@MyAnnotation注解的类");
System.out.println("Class1");
System.out.println("Class2");
System.out.println("Class3");
}
创建method builder
在javapoet中,方法对应的类是MethodSpec
。首先通过建造者模式来创建一个builder同时指定方法名、通过addModifiers
指定可见性、通过returns
指定返回值类型:
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("print") // 函数名
.addModifiers(Modifier.PUBLIC) // 添加限定符
.returns(void.class); // 返回类型
通过addStatement
方法,可以在方法体中添加语句。这里打印一句话“以下是标注了@MyAnnotation注解的类”
:
methodBuilder.addStatement("$T.out.println(\"以下是标注了@MyAnnotation注解的类\")", System.class); // 添加语句
扫描所有标注了@MyAnnotation的类并打印出来
在2.3.1中已经介绍到,可以通过process
方法的第二个参数获取所有标注了某个标记的类。然后再在方法中添加打印语句,将这些类的名称打印出来:
// 标注了@MyAnnotation的节点
Set<? extends Element> rootElements = rEnv.getElementsAnnotatedWith(MyAnnotation.class);
// 查询所有标注了@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(); // 完成构造
到这里,目标方法就已经构建完成了。
2.3.3 通过javapoet生成类
与生成方法类似,生成类也是通过构造者模式,并可以通过addMethod
将之前生成的方法添加到这个类中:
// 生成类
TypeSpec myClass = TypeSpec.classBuilder("AptGeneratedClass") // 类名
.addModifiers(Modifier.PUBLIC) // public类
.addMethod(method) // 添加上述方法
.build(); // 构造类
这里将生成的类名命名为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); // 通过文件工具创建文件
} catch (IOException e) {
e.printStackTrace();
}
至此,就通过APT完成了一个最简单的可以自动生成文件的注解处理器。
2.4 包含注释的MyProcessor完整代码
/**
* 自定义APT类
* <p>
* TypeElement:类元素
* <p>
* 对于Java语言来讲,将其看做结构化的语言模型,那么就分为了:
* PackageElement包元素,
* TypeElement类元素,
* TypeParameterElement泛型元素,
* VariableElement变量元素
* ExecutableElement可执行元素(方法)
* <p>
* 见{@link javax.lang.model.element.Element}
*/
@AutoService(Processor.class)
@SupportedOptions("my_param") // 接收外来参数的key
@SupportedAnnotationTypes("top.littlefogcat.apt_annotation.MyAnnotation") // 支持的注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 支持的Java版本
public class MyProcessor extends AbstractProcessor {
/*
* 一些工具,在init方法中通过环境对象获取
*/
private Types mTypeUtils;
private Elements mElementUtils;
private Messager mMessager;
private Filer mFiler; // 文件工具
private String mParam;
/**
* 做一些初始化的工作,可以通过pEnv参数获取一些工具类。
* 同时,通过`SupportedOptions`配置的参数也可以在这里获取。
*
* @param pEnv 环境对象,提供一些工具
*/
@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"); // 获取外界传入参数
}
/**
* @param types 需要处理的注解集合
* @param rEnv 运行环境?通过这个对象查询节点信息
* @return 处理成功返回true,否则返回false
*/
@Override
public boolean process(Set<? extends TypeElement> types, RoundEnvironment rEnv) {
if (types == null || types.isEmpty()) {
return false;
}
// 生成一个函数格式如:
// public void print() {
// System.out.println("以下是标注了@MyAnnotation注解的类");
// System.out.println("AnnotedClass1");
// System.out.println("AnnotedClass2");
// }
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("print") // 函数名
.addModifiers(Modifier.PUBLIC) // 添加限定符
.returns(void.class) // 返回类型
.addStatement("$T.out.println(\"以下是标注了@MyAnnotation注解的类\")", System.class); // 添加语句
// 标注了@MyAnnotation的节点
Set<? extends Element> rootElements = rEnv.getElementsAnnotatedWith(MyAnnotation.class);
// 查询所有标注了@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(); // 完成构造
// 生成类
TypeSpec myClass = TypeSpec.classBuilder("AptGeneratedClass") // 类名
.addModifiers(Modifier.PUBLIC) // public类
.addMethod(method) // 添加上述方法
.build(); // 构造类
// 生成文件
JavaFile javaFile = JavaFile.builder("top.littlefogcat.apt", myClass) // 包名、类型
.build();
try {
javaFile.writeTo(mFiler); // 通过文件工具创建文件
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
/**
* 接受外来参数,比如在build.gradle中的javaCompileOptions.annotationProcessorOptions配置
* <p>
* 也可以通过`@SupportedOptions`注解来配置
*/
@Override
public Set<String> getSupportedOptions() {
return Collections.singleton("my_param");
}
/**
* 返回当前注解处理器支持的注解类型
* <p>
* 也可以通过`@SupportedAnnotationTypes`注解来配置
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton("top.littlefogcat.apt_annotation.MyAnnotation");
}
/**
* 返回当前注解处理器支持的JDK版本
* <p>
* 也可以通过`@SupportedSourceVersion`注解来配置
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
}
三、测试效果
在apt-app
模块中创建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
已经生成完毕了,并且打印出了标注了@MyAnnotation注解的类(MainActivity)。