【Android】APT——注解处理器(一):初窥

在上一篇文章《注解实例 - 实现一个简单的@Autowired运行时注解》中,介绍了如何通过一个运行时注解来实现一个简单的依赖注入工具。
虽然使用方便,但是运行时注解是有一个硬伤的,那就是使用时需要进行大量扫描和反射操作,会对运行效率造成一定影响。同时,一些功能需要自动生成代码来提供,这时候,就需要用到APT了。当然,这里的APT指的不是信息安全中的APT,而是Annotation Processor Tool,即注解处理器工具。

本文将介绍如何写出一个最简单的APT demo,通过APT处理注解并自动生成文件,其中关于注解的知识就不再介绍了。

首先明确Demo目标:

  1. 创建一个类注解MyAnnotation以及自定义注解处理器MyProcessor
  2. 通过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 types, RoundEnvironment rEnv) {}
}

其中类上面三个注解依次代表了注解处理器接受外部参数的key(暂时没有用到)、注解处理器支持的注解类型(即1.3中创建的注解全名)、注解处理器支持的Java版本。这三个配置也可以通过重写类中的方法来实现,不过通过注解更加简便明了。

2.2 重写init方法

AbstractProcessorinit方法提供了一个环境对象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 typesRoundEnvironment 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 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类
 * 

* TypeElement:类元素 *

* 对于Java语言来讲,将其看做结构化的语言模型,那么就分为了: * PackageElement包元素, * TypeElement类元素, * TypeParameterElement泛型元素, * VariableElement变量元素 * ExecutableElement可执行元素(方法) *

* 见{@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 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 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配置 *

* 也可以通过`@SupportedOptions`注解来配置 */ @Override public Set getSupportedOptions() { return Collections.singleton("my_param"); } /** * 返回当前注解处理器支持的注解类型 *

* 也可以通过`@SupportedAnnotationTypes`注解来配置 */ @Override public Set getSupportedAnnotationTypes() { return Collections.singleton("top.littlefogcat.apt_annotation.MyAnnotation"); } /** * 返回当前注解处理器支持的JDK版本 *

* 也可以通过`@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)。

生成文件

生成文件

四、参考资料

《Android APT 系列 (三):APT 技术探究》
《JavaPoet - 优雅地生成代码》

你可能感兴趣的:(【Android】APT——注解处理器(一):初窥)