美文网首页Android进阶
【Android】APT——注解处理器(一):初窥

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

作者: littlefogcat | 来源:发表于2021-09-10 06:09 被阅读0次

    在上一篇文章《注解实例 - 实现一个简单的@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<? extends TypeElement> 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<? extends TypeElement> 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<? 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)。

    生成文件
    生成文件

    四、参考资料

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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/rghgwltx.html